You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by as...@apache.org on 2026/05/02 13:51:35 UTC
(grails-website) branch asf-site-production updated: Updating apache/grails-website asf-site-production branch for GitHub Actions run:25253242549
This is an automated email from the ASF dual-hosted git repository.
asf-ci-deploy pushed a commit to branch asf-site-production
in repository https://gitbox.apache.org/repos/asf/grails-website.git
The following commit(s) were added to refs/heads/asf-site-production by this push:
new 299a82f238d Updating apache/grails-website asf-site-production branch for GitHub Actions run:25253242549
299a82f238d is described below
commit 299a82f238db16077911b6003daea3db0207c76c
Author: jamesfredley <ja...@users.noreply.github.com>
AuthorDate: Sat May 2 13:51:28 2026 +0000
Updating apache/grails-website asf-site-production branch for GitHub Actions run:25253242549
---
guides/_redirects.json | 2 +-
guides/adding-commit-info/3/guide/index.html | 15 +-
.../3/guide/pages/gradlePlugin.html | 15 +-
guides/adding-commit-info/3/guide/single.html | 15 +-
.../adding-commit-info/3/guide/writingTheApp.html | 15 +-
guides/adding-commit-info/4/guide/index.html | 15 +-
.../4/guide/pages/gradlePlugin.html | 15 +-
guides/adding-commit-info/4/guide/single.html | 15 +-
.../adding-commit-info/4/guide/writingTheApp.html | 15 +-
.../4/guide/index.html | 457 ++++++++++-
.../4/guide/pages/detailActivity.html | 69 +-
.../4/guide/pages/networkingCode.html | 294 ++++++-
.../4/guide/pages/v2Android.html | 94 ++-
.../4/guide/single.html | 457 ++++++++++-
.../4/guide/version2.html | 94 ++-
.../4/guide/writingTheAndroidApp.html | 363 +++++++-
.../4/guide/index.html | 909 ++++++++++++++++++++-
.../4/guide/pages/detailViewController.html | 71 +-
.../4/guide/pages/iosJsonToModel.html | 238 +++++-
.../4/guide/pages/iosModel.html | 16 +-
.../4/guide/pages/networkingCode.html | 363 +++++++-
.../4/guide/pages/v2iOS.html | 221 ++++-
.../4/guide/single.html | 909 ++++++++++++++++++++-
.../4/guide/version2.html | 221 ++++-
.../4/guide/writingTheiOSApp.html | 688 +++++++++++++++-
.../4/guide/index.html | 435 +++++++++-
.../4/guide/pages/detailViewController.html | 66 +-
.../4/guide/pages/iosJsonToModel.html | 24 +-
.../4/guide/pages/iosModel.html | 11 +-
.../4/guide/pages/networkingCode.html | 175 +++-
.../4/guide/pages/v2iOS.html | 159 +++-
.../4/guide/single.html | 435 +++++++++-
.../4/guide/version2.html | 159 +++-
.../4/guide/writingTheiOSApp.html | 276 ++++++-
guides/discriminator-per-tenant/4/guide/index.html | 17 +-
.../4/guide/pages/tenantSelection.html | 17 +-
.../discriminator-per-tenant/4/guide/single.html | 17 +-
.../4/guide/writingTheApp.html | 17 +-
guides/grails-basicauth/3/guide/index.html | 101 ++-
.../grails-basicauth/3/guide/pages/controller.html | 34 +-
.../3/guide/pages/dependencies.html | 4 +-
.../grails-basicauth/3/guide/pages/services.html | 14 +-
guides/grails-basicauth/3/guide/pages/test.html | 41 +-
guides/grails-basicauth/3/guide/pages/views.html | 8 +-
guides/grails-basicauth/3/guide/single.html | 101 ++-
guides/grails-basicauth/3/guide/writingTheApp.html | 101 ++-
guides/grails-basicauth/4/guide/index.html | 109 ++-
.../grails-basicauth/4/guide/pages/controller.html | 34 +-
.../4/guide/pages/dependencies.html | 4 +-
.../grails-basicauth/4/guide/pages/services.html | 14 +-
guides/grails-basicauth/4/guide/pages/test.html | 49 +-
guides/grails-basicauth/4/guide/pages/views.html | 8 +-
guides/grails-basicauth/4/guide/single.html | 109 ++-
guides/grails-basicauth/4/guide/writingTheApp.html | 109 ++-
.../4/guide/index.html | 56 +-
.../4/guide/pages/acceptanceTests.html | 17 +-
.../4/guide/pages/configurationProperties.html | 18 +-
.../4/guide/pages/taglib.html | 21 +-
.../4/guide/single.html | 56 +-
.../4/guide/writingTheApp.html | 56 +-
.../6/guide/index.html | 56 +-
.../6/guide/pages/acceptanceTests.html | 17 +-
.../6/guide/pages/configurationProperties.html | 18 +-
.../6/guide/pages/taglib.html | 21 +-
.../6/guide/single.html | 56 +-
.../6/guide/writingTheApp.html | 56 +-
.../3/guide/index.html | 91 ++-
.../3/guide/pages/acceptanceTests.html | 19 +-
.../3/guide/pages/configurationProperties.html | 34 +-
.../3/guide/pages/taglib.html | 18 +-
.../3/guide/pages/tests.html | 20 +-
.../3/guide/single.html | 91 ++-
.../3/guide/writingTheApp.html | 91 ++-
.../4/guide/index.html | 94 ++-
.../4/guide/pages/acceptanceTests.html | 18 +-
.../4/guide/pages/configurationProperties.html | 34 +-
.../4/guide/pages/taglib.html | 18 +-
.../4/guide/pages/tests.html | 24 +-
.../4/guide/single.html | 94 ++-
.../4/guide/writingTheApp.html | 94 ++-
guides/grails-email/3/guide/index.html | 409 ++++++++-
.../grails-email/3/guide/pages/awsSesService.html | 144 +++-
guides/grails-email/3/guide/pages/controller.html | 54 +-
guides/grails-email/3/guide/pages/resources.html | 15 +-
.../3/guide/pages/sendGridService.html | 106 ++-
guides/grails-email/3/guide/pages/services.html | 274 ++++++-
guides/grails-email/3/guide/pages/test.html | 66 +-
guides/grails-email/3/guide/single.html | 409 ++++++++-
guides/grails-email/3/guide/writingTheApp.html | 409 ++++++++-
guides/grails-email/4/guide/index.html | 367 ++++++++-
.../grails-email/4/guide/pages/awsSesService.html | 94 ++-
guides/grails-email/4/guide/pages/controller.html | 54 +-
guides/grails-email/4/guide/pages/resources.html | 13 +-
.../4/guide/pages/sendGridService.html | 112 ++-
guides/grails-email/4/guide/pages/services.html | 230 +++++-
guides/grails-email/4/guide/pages/test.html | 70 +-
guides/grails-email/4/guide/single.html | 367 ++++++++-
guides/grails-email/4/guide/writingTheApp.html | 367 ++++++++-
.../grails-file-download-excel/3/guide/index.html | 253 +++++-
.../3/guide/pages/books.html | 30 +-
.../3/guide/pages/controller.html | 36 +-
.../3/guide/pages/excel.html | 58 +-
.../3/guide/pages/spreadsheetBuilder.html | 3 +-
.../3/guide/pages/tests.html | 126 ++-
.../grails-file-download-excel/3/guide/single.html | 253 +++++-
.../3/guide/writingTheApp.html | 253 +++++-
.../grails-file-download-excel/4/guide/index.html | 266 +++++-
.../4/guide/pages/books.html | 30 +-
.../4/guide/pages/controller.html | 36 +-
.../4/guide/pages/excel.html | 58 +-
.../4/guide/pages/spreadsheetBuilder.html | 3 +-
.../4/guide/pages/tests.html | 139 +++-
.../grails-file-download-excel/4/guide/single.html | 266 +++++-
.../4/guide/writingTheApp.html | 266 +++++-
rss.xml | 2 +-
115 files changed, 14397 insertions(+), 663 deletions(-)
diff --git a/guides/_redirects.json b/guides/_redirects.json
index 928f9ba4da2..322357c4666 100644
--- a/guides/_redirects.json
+++ b/guides/_redirects.json
@@ -1,6 +1,6 @@
{
"version": 1,
- "generated": "2026-05-02T13:17:25.054483945Z",
+ "generated": "2026-05-02T13:44:41.895297772Z",
"from_base": "https://guides.grails.org",
"to_base": "https://grails.apache.org/guides",
"count": 1731,
diff --git a/guides/adding-commit-info/3/guide/index.html b/guides/adding-commit-info/3/guide/index.html
index 9173334c606..703a85bf956 100644
--- a/guides/adding-commit-info/3/guide/index.html
+++ b/guides/adding-commit-info/3/guide/index.html
@@ -452,7 +452,20 @@ The build-info plugin works by utilizing the environment variables available via
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/3/guide/pages/gradlePlugin.html b/guides/adding-commit-info/3/guide/pages/gradlePlugin.html
index f5b882b1f45..34dfc301f4f 100644
--- a/guides/adding-commit-info/3/guide/pages/gradlePlugin.html
+++ b/guides/adding-commit-info/3/guide/pages/gradlePlugin.html
@@ -95,7 +95,20 @@ function addJsClass() {
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/3/guide/single.html b/guides/adding-commit-info/3/guide/single.html
index 9173334c606..703a85bf956 100644
--- a/guides/adding-commit-info/3/guide/single.html
+++ b/guides/adding-commit-info/3/guide/single.html
@@ -452,7 +452,20 @@ The build-info plugin works by utilizing the environment variables available via
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/3/guide/writingTheApp.html b/guides/adding-commit-info/3/guide/writingTheApp.html
index a04ecc7ab0f..8fb7c16fbdd 100644
--- a/guides/adding-commit-info/3/guide/writingTheApp.html
+++ b/guides/adding-commit-info/3/guide/writingTheApp.html
@@ -133,7 +133,20 @@ function addJsClass() {
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/4/guide/index.html b/guides/adding-commit-info/4/guide/index.html
index f77be79e2cd..0be4a0c1ded 100644
--- a/guides/adding-commit-info/4/guide/index.html
+++ b/guides/adding-commit-info/4/guide/index.html
@@ -452,7 +452,20 @@ The build-info plugin works by utilizing the environment variables available via
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/4/guide/pages/gradlePlugin.html b/guides/adding-commit-info/4/guide/pages/gradlePlugin.html
index e211b405c47..97bca37c75c 100644
--- a/guides/adding-commit-info/4/guide/pages/gradlePlugin.html
+++ b/guides/adding-commit-info/4/guide/pages/gradlePlugin.html
@@ -95,7 +95,20 @@ function addJsClass() {
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/4/guide/single.html b/guides/adding-commit-info/4/guide/single.html
index f77be79e2cd..0be4a0c1ded 100644
--- a/guides/adding-commit-info/4/guide/single.html
+++ b/guides/adding-commit-info/4/guide/single.html
@@ -452,7 +452,20 @@ The build-info plugin works by utilizing the environment variables available via
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/adding-commit-info/4/guide/writingTheApp.html b/guides/adding-commit-info/4/guide/writingTheApp.html
index e9a3613e6e1..a5518d3f7a0 100644
--- a/guides/adding-commit-info/4/guide/writingTheApp.html
+++ b/guides/adding-commit-info/4/guide/writingTheApp.html
@@ -133,7 +133,20 @@ function addJsClass() {
The goal of this plugin is to capture the commit information and store it in a file called <code>git.properties</code></p>
</div>
<div class="paragraph">
-<p><a href="{commondir}/commmon-addingAGradlePlugin.adoc" class="bare include">{commondir}/commmon-addingAGradlePlugin.adoc</a></p>
+<p>Adding a Gradle plugin is fairly straight forward. This involves a few steps.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Adding the dependency to the <code>buildscript { …​ }</code> block in <code>build.gradle</code>. This means that you are adding the dependency to the build and not your deliverable application.</p>
+</li>
+<li>
+<p>Ensuring the build script dependency will be resolved via the appropriate maven repositories</p>
+</li>
+<li>
+<p>Applying the plugin</p>
+</li>
+</ol>
</div>
<div class="listingblock">
<div class="title">/complete/build.gradle</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/index.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/index.html
index 87898282de4..fbb66ed64cd 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/index.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/index.html
@@ -877,7 +877,13 @@ dependency is added when we create an app with the <em>rest-api</em> profile.</p
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>; <i class="conum" data-value="1"></i><b>(1)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>; <i class="conum" data-value="2"></i><b>(2)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">1.0</span><span class="delimiter">"</span></span>; <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -916,7 +922,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/model/Announcement.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/model/Announcement.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network.model</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Announcement</span> {
+ <span class="directive">private</span> <span class="predefined-type">Long</span> id;
+ <span class="directive">private</span> <span class="predefined-type">String</span> title;
+ <span class="directive">private</span> <span class="predefined-type">String</span> body;
+
+ <span class="directive">public</span> <span class="predefined-type">Long</span> getId() {
+ <span class="keyword">return</span> id;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setId(<span class="predefined-type">Long</span> id) {
+ <span class="local-variable">this</span>.id = id;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getTitle() {
+ <span class="keyword">return</span> title;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setTitle(<span class="predefined-type">String</span> title) {
+ <span class="local-variable">this</span>.title = title;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getBody() {
+ <span class="keyword">return</span> body;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setBody(<span class="predefined-type">String</span> body) {
+ <span class="local-variable">this</span>.body = body;
+ }
+}</code></pre>
</div>
</div>
</div>
@@ -928,7 +964,7 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><uses-permission</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">android.permission.INTERNET</span><span class="delimiter">"</span></span><span class="tag">/></span></code></pre>
</div>
</div>
<div class="sect3">
@@ -943,7 +979,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.squareup.okhttp3:okhttp:4.2.2</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
</div>
@@ -965,7 +1001,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.google.code.gson:gson:2.8.6</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -975,7 +1011,18 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/NetworkTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/NetworkTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+
+<span class="type">class</span> <span class="class">NetworkTask</span> {
+ <span class="directive">static</span> Request requestWithUrl(<span class="predefined-type">String</span> url) {
+ <span class="keyword">return</span> <span class="keyword">new</span> Request.Builder()
+ .url(url)
+ .header(<span class="string"><span class="delimiter">"</span><span class="content">Accept-Version</span><span class="delimiter">"</span></span>, Constants.ACCEPT_VERSION)
+ .build();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -984,7 +1031,49 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">android.util.Log</span>;
+
+<span class="keyword">import</span> <span class="include">com.google.gson.Gson</span>;
+<span class="keyword">import</span> <span class="include">com.google.gson.reflect.TypeToken</span>;
+
+<span class="keyword">import</span> <span class="include">java.io.IOException</span>;
+<span class="keyword">import</span> <span class="include">java.lang.reflect.Type</span>;
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.OkHttpClient</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Response</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementsFetcher</span> {
+
+ <span class="directive">private</span> <span class="directive">final</span> <span class="directive">static</span> <span class="predefined-type">String</span> TAG = AnnouncementsFetcher.class.getSimpleName();
+
+ <span class="directive">private</span> OkHttpClient client = <span class="keyword">new</span> OkHttpClient();
+ <span class="directive">private</span> <span class="directive">final</span> Gson gson = <span class="keyword">new</span> Gson();
+
+ <span class="directive">public</span> <span class="predefined-type">List</span><Announcement> fetchAnnouncements() {
+ <span class="predefined-type">Type</span> listType = <span class="keyword">new</span> TypeToken<<span class="predefined-type">List</span><Announcement>>() {}.getType();
+ <span class="keyword">try</span> {
+ <span class="predefined-type">String</span> url = Constants.GRAILS_APP_URL + Constants.ANNOUNCEMENTS_PATH;
+ <span class="predefined-type">String</span> jsonString = fetchAnnouncementsJsonString(url);
+ <span class="keyword">return</span> gson.fromJson(jsonString, listType);
+
+ } <span class="keyword">catch</span> (<span class="exception">IOException</span> e) {
+ Log.e(TAG, e.toString());
+ <span class="keyword">return</span> <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><>();
+ }
+ }
+
+ <span class="directive">private</span> <span class="predefined-type">String</span> fetchAnnouncementsJsonString(<span class="predefined-type">String</span> url) <span class="directive">throws</span> <span class="exception">IOException</span> {
+ Request request = NetworkTask.requestWithUrl(url);
+ Response response = client.newCall(request).execute();
+ <span class="keyword">return</span> response.body().string();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -994,7 +1083,36 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementsFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementsTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Void</span>, <span class="predefined-type">Void</span>, <span class="predefined-type">List</span><Announcement>> {
+
+ <span class="directive">private</span> AnnouncementsFetcher fetcher = <span class="keyword">new</span> AnnouncementsFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementsDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementsTask(RetrieveAnnouncementsDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="predefined-type">List</span><Announcement> doInBackground(<span class="predefined-type">Void</span>... voids) {
+ <span class="keyword">return</span> fetcher.fetchAnnouncements();
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(<span class="predefined-type">List</span><Announcement> announcements) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementsFetched(announcements);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1003,7 +1121,15 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementsDelegate</span> {
+ <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1012,7 +1138,62 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/MainActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/android/activities/MainActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ListView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.adapters.AnnouncementAdapter</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementsTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">MainActivity</span> <span class="directive">extends</span> Activity
+ <span class="directive">implements</span> RetrieveAnnouncementsDelegate, AnnouncementAdapterDelegate {
+
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_ID = <span class="string"><span class="delimiter">"</span><span class="content">id</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_TITLE = <span class="string"><span class="delimiter">"</span><span class="content">title</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_BODY = <span class="string"><span class="delimiter">"</span><span class="content">body</span><span class="delimiter">"</span></span>;
+ <span class="directive">private</span> AnnouncementAdapter adapter;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ <span class="predefined-type">ListView</span> announcementsListView = (<span class="predefined-type">ListView</span>) findViewById(R.id.announcementsListView);
+ adapter = <span class="keyword">new</span> AnnouncementAdapter(<span class="local-variable">this</span>, <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><Announcement>(), <span class="local-variable">this</span>);
+ announcementsListView.setAdapter(adapter);
+
+ <span class="keyword">new</span> RetrieveAnnouncementsTask(<span class="local-variable">this</span>).execute(); <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements) { <i class="conum" data-value="2"></i><b>(2)</b>
+ adapter.clear();
+ adapter.addAll(announcements);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementTapped(Announcement announcement) {
+ segueToAnnouncementActivity(announcement);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> segueToAnnouncementActivity(Announcement announcement) {
+ Intent i = <span class="keyword">new</span> Intent(<span class="local-variable">this</span>, AnnouncementActivity.class);
+ i.putExtra(EXTRA_ID, announcement.getId());
+ i.putExtra(EXTRA_TITLE, announcement.getTitle());
+ i.putExtra(EXTRA_BODY, announcement.getBody());
+ startActivity(i);
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1033,7 +1214,25 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_main.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_main.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><ListView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/announcementsListView</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span><span class="tag">></span>
+ <span class="tag"></ListView></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -1042,19 +1241,86 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.adapters</span>;
+
+<span class="keyword">import</span> <span class="include">android.content.Context</span>;
+<span class="keyword">import</span> <span class="include">android.view.LayoutInflater</span>;
+<span class="keyword">import</span> <span class="include">android.view.View</span>;
+<span class="keyword">import</span> <span class="include">android.view.ViewGroup</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ArrayAdapter</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">androidx.annotation.NonNull</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementAdapter</span> <span class="directive">extends</span> ArrayAdapter<Announcement> {
+ <span class="directive">private</span> AnnouncementAdapterDelegate delegate;
+
+ <span class="directive">public</span> AnnouncementAdapter(<span class="predefined-type">Context</span> context, <span class="predefined-type">List</span><Announcement> announcements, AnnouncementAdapterDelegate delegate) {
+ <span class="local-variable">super</span>(context, <span class="integer">0</span>, announcements);
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@NonNull</span>
+ <span class="directive">public</span> <span class="predefined-type">View</span> getView(<span class="type">int</span> position, <span class="predefined-type">View</span> convertView, <span class="annotation">@NonNull</span> ViewGroup parent) {
+ <span class="keyword">if</span> (convertView == <span class="predefined-constant">null</span>) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_announcement, parent, <span class="predefined-constant">false</span>);
+ }
+ TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
+ Announcement announcement = getItem(position);
+ <span class="keyword">if</span> ( announcement != <span class="predefined-constant">null</span> ) {
+ tvTitle.setText(announcement.getTitle());
+ tvTitle.setOnClickListener(<span class="keyword">new</span> AnnouncementClickListener(announcement));
+ }
+ <span class="keyword">return</span> convertView;
+ }
+
+ <span class="directive">private</span> <span class="type">class</span> <span class="class">AnnouncementClickListener</span> <span class="directive">implements</span> <span class="predefined-type">View</span>.OnClickListener {
+ <span class="directive">private</span> Announcement announcement;
+
+ AnnouncementClickListener(Announcement announcement) {
+ <span class="local-variable">this</span>.announcement = announcement;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onClick(<span class="predefined-type">View</span> view) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementTapped(announcement);
+ }
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">AnnouncementAdapterDelegate</span> {
+ <span class="type">void</span> onAnnouncementTapped(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/item_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/item_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag"><LinearLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span> <span class="tag">></span>
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span><span class="tag">/></span>
+<span class="tag"></LinearLayout></span></code></pre>
</div>
</div>
</div>
@@ -1084,19 +1350,82 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><activity</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">.android.activities.AnnouncementActivity</span><span class="delimiter">"</span></span> <span class="tag">/></span></code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+ populateUi();
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUi() {
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">String</span> title = intent.getStringExtra(MainActivity.EXTRA_TITLE);
+ <span class="directive">final</span> <span class="predefined-type">String</span> body = intent.getStringExtra(MainActivity.EXTRA_BODY);
+ populateUiWithTitleAndBody(title, body);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:text</span>=<span class="string"><span class="delimiter">"</span><span class="content">Announcement Title</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:textSize</span>=<span class="string"><span class="delimiter">"</span><span class="content">20dp</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+ <span class="tag"><WebView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/wvBody</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_below</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
@@ -1340,7 +1669,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">2.0</span><span class="delimiter">"</span></span>;
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1349,7 +1684,39 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Long</span>, <span class="predefined-type">Void</span>, Announcement> {
+ <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> TAG = RetrieveAnnouncementTask.class.getSimpleName();
+ AnnouncementFetcher fetcher = <span class="keyword">new</span> AnnouncementFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementTask(RetrieveAnnouncementDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> Announcement doInBackground(<span class="predefined-type">Long</span>... ids) {
+ <span class="keyword">if</span> ( ids != <span class="predefined-constant">null</span> && ids.length >= <span class="integer">1</span>) {
+ <span class="predefined-type">Long</span> announcementId = ids[<span class="integer">0</span>];
+ <span class="keyword">return</span> fetcher.fetchAnnouncement(announcementId);
+ }
+
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>;
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(Announcement announcement) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementFetched(announcement);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1358,7 +1725,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementDelegate</span> {
+ <span class="type">void</span> onAnnouncementFetched(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1367,7 +1740,49 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity <span class="directive">implements</span> RetrieveAnnouncementDelegate {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">Long</span> announcementId = intent.getLongExtra(MainActivity.EXTRA_ID, <span class="integer">0L</span>);
+ <span class="keyword">new</span> RetrieveAnnouncementTask(<span class="local-variable">this</span>).execute(announcementId);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementFetched(Announcement announcement) {
+ populateUiWithTitleAndBody(announcement.getTitle(), announcement.getBody());
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/detailActivity.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/detailActivity.html
index 9a04491daec..18c81258e36 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/detailActivity.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/detailActivity.html
@@ -104,19 +104,82 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><activity</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">.android.activities.AnnouncementActivity</span><span class="delimiter">"</span></span> <span class="tag">/></span></code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+ populateUi();
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUi() {
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">String</span> title = intent.getStringExtra(MainActivity.EXTRA_TITLE);
+ <span class="directive">final</span> <span class="predefined-type">String</span> body = intent.getStringExtra(MainActivity.EXTRA_BODY);
+ populateUiWithTitleAndBody(title, body);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:text</span>=<span class="string"><span class="delimiter">"</span><span class="content">Announcement Title</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:textSize</span>=<span class="string"><span class="delimiter">"</span><span class="content">20dp</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+ <span class="tag"><WebView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/wvBody</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_below</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
index 54ffc87e4bf..d5755c73dfe 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
@@ -96,7 +96,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>; <i class="conum" data-value="1"></i><b>(1)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>; <i class="conum" data-value="2"></i><b>(2)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">1.0</span><span class="delimiter">"</span></span>; <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -135,7 +141,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/model/Announcement.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/model/Announcement.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network.model</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Announcement</span> {
+ <span class="directive">private</span> <span class="predefined-type">Long</span> id;
+ <span class="directive">private</span> <span class="predefined-type">String</span> title;
+ <span class="directive">private</span> <span class="predefined-type">String</span> body;
+
+ <span class="directive">public</span> <span class="predefined-type">Long</span> getId() {
+ <span class="keyword">return</span> id;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setId(<span class="predefined-type">Long</span> id) {
+ <span class="local-variable">this</span>.id = id;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getTitle() {
+ <span class="keyword">return</span> title;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setTitle(<span class="predefined-type">String</span> title) {
+ <span class="local-variable">this</span>.title = title;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getBody() {
+ <span class="keyword">return</span> body;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setBody(<span class="predefined-type">String</span> body) {
+ <span class="local-variable">this</span>.body = body;
+ }
+}</code></pre>
</div>
</div>
</div>
@@ -147,7 +183,7 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><uses-permission</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">android.permission.INTERNET</span><span class="delimiter">"</span></span><span class="tag">/></span></code></pre>
</div>
</div>
<div class="sect3">
@@ -162,7 +198,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.squareup.okhttp3:okhttp:4.2.2</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
</div>
@@ -184,7 +220,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.google.code.gson:gson:2.8.6</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -194,7 +230,18 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/NetworkTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/NetworkTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+
+<span class="type">class</span> <span class="class">NetworkTask</span> {
+ <span class="directive">static</span> Request requestWithUrl(<span class="predefined-type">String</span> url) {
+ <span class="keyword">return</span> <span class="keyword">new</span> Request.Builder()
+ .url(url)
+ .header(<span class="string"><span class="delimiter">"</span><span class="content">Accept-Version</span><span class="delimiter">"</span></span>, Constants.ACCEPT_VERSION)
+ .build();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -203,7 +250,49 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">android.util.Log</span>;
+
+<span class="keyword">import</span> <span class="include">com.google.gson.Gson</span>;
+<span class="keyword">import</span> <span class="include">com.google.gson.reflect.TypeToken</span>;
+
+<span class="keyword">import</span> <span class="include">java.io.IOException</span>;
+<span class="keyword">import</span> <span class="include">java.lang.reflect.Type</span>;
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.OkHttpClient</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Response</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementsFetcher</span> {
+
+ <span class="directive">private</span> <span class="directive">final</span> <span class="directive">static</span> <span class="predefined-type">String</span> TAG = AnnouncementsFetcher.class.getSimpleName();
+
+ <span class="directive">private</span> OkHttpClient client = <span class="keyword">new</span> OkHttpClient();
+ <span class="directive">private</span> <span class="directive">final</span> Gson gson = <span class="keyword">new</span> Gson();
+
+ <span class="directive">public</span> <span class="predefined-type">List</span><Announcement> fetchAnnouncements() {
+ <span class="predefined-type">Type</span> listType = <span class="keyword">new</span> TypeToken<<span class="predefined-type">List</span><Announcement>>() {}.getType();
+ <span class="keyword">try</span> {
+ <span class="predefined-type">String</span> url = Constants.GRAILS_APP_URL + Constants.ANNOUNCEMENTS_PATH;
+ <span class="predefined-type">String</span> jsonString = fetchAnnouncementsJsonString(url);
+ <span class="keyword">return</span> gson.fromJson(jsonString, listType);
+
+ } <span class="keyword">catch</span> (<span class="exception">IOException</span> e) {
+ Log.e(TAG, e.toString());
+ <span class="keyword">return</span> <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><>();
+ }
+ }
+
+ <span class="directive">private</span> <span class="predefined-type">String</span> fetchAnnouncementsJsonString(<span class="predefined-type">String</span> url) <span class="directive">throws</span> <span class="exception">IOException</span> {
+ Request request = NetworkTask.requestWithUrl(url);
+ Response response = client.newCall(request).execute();
+ <span class="keyword">return</span> response.body().string();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -213,7 +302,36 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementsFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementsTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Void</span>, <span class="predefined-type">Void</span>, <span class="predefined-type">List</span><Announcement>> {
+
+ <span class="directive">private</span> AnnouncementsFetcher fetcher = <span class="keyword">new</span> AnnouncementsFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementsDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementsTask(RetrieveAnnouncementsDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="predefined-type">List</span><Announcement> doInBackground(<span class="predefined-type">Void</span>... voids) {
+ <span class="keyword">return</span> fetcher.fetchAnnouncements();
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(<span class="predefined-type">List</span><Announcement> announcements) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementsFetched(announcements);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -222,7 +340,15 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementsDelegate</span> {
+ <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -231,7 +357,62 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/MainActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/android/activities/MainActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ListView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.adapters.AnnouncementAdapter</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementsTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">MainActivity</span> <span class="directive">extends</span> Activity
+ <span class="directive">implements</span> RetrieveAnnouncementsDelegate, AnnouncementAdapterDelegate {
+
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_ID = <span class="string"><span class="delimiter">"</span><span class="content">id</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_TITLE = <span class="string"><span class="delimiter">"</span><span class="content">title</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_BODY = <span class="string"><span class="delimiter">"</span><span class="content">body</span><span class="delimiter">"</span></span>;
+ <span class="directive">private</span> AnnouncementAdapter adapter;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ <span class="predefined-type">ListView</span> announcementsListView = (<span class="predefined-type">ListView</span>) findViewById(R.id.announcementsListView);
+ adapter = <span class="keyword">new</span> AnnouncementAdapter(<span class="local-variable">this</span>, <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><Announcement>(), <span class="local-variable">this</span>);
+ announcementsListView.setAdapter(adapter);
+
+ <span class="keyword">new</span> RetrieveAnnouncementsTask(<span class="local-variable">this</span>).execute(); <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements) { <i class="conum" data-value="2"></i><b>(2)</b>
+ adapter.clear();
+ adapter.addAll(announcements);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementTapped(Announcement announcement) {
+ segueToAnnouncementActivity(announcement);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> segueToAnnouncementActivity(Announcement announcement) {
+ Intent i = <span class="keyword">new</span> Intent(<span class="local-variable">this</span>, AnnouncementActivity.class);
+ i.putExtra(EXTRA_ID, announcement.getId());
+ i.putExtra(EXTRA_TITLE, announcement.getTitle());
+ i.putExtra(EXTRA_BODY, announcement.getBody());
+ startActivity(i);
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -252,7 +433,25 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_main.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_main.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><ListView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/announcementsListView</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span><span class="tag">></span>
+ <span class="tag"></ListView></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -261,19 +460,86 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.adapters</span>;
+
+<span class="keyword">import</span> <span class="include">android.content.Context</span>;
+<span class="keyword">import</span> <span class="include">android.view.LayoutInflater</span>;
+<span class="keyword">import</span> <span class="include">android.view.View</span>;
+<span class="keyword">import</span> <span class="include">android.view.ViewGroup</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ArrayAdapter</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">androidx.annotation.NonNull</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementAdapter</span> <span class="directive">extends</span> ArrayAdapter<Announcement> {
+ <span class="directive">private</span> AnnouncementAdapterDelegate delegate;
+
+ <span class="directive">public</span> AnnouncementAdapter(<span class="predefined-type">Context</span> context, <span class="predefined-type">List</span><Announcement> announcements, AnnouncementAdapterDelegate delegate) {
+ <span class="local-variable">super</span>(context, <span class="integer">0</span>, announcements);
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@NonNull</span>
+ <span class="directive">public</span> <span class="predefined-type">View</span> getView(<span class="type">int</span> position, <span class="predefined-type">View</span> convertView, <span class="annotation">@NonNull</span> ViewGroup parent) {
+ <span class="keyword">if</span> (convertView == <span class="predefined-constant">null</span>) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_announcement, parent, <span class="predefined-constant">false</span>);
+ }
+ TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
+ Announcement announcement = getItem(position);
+ <span class="keyword">if</span> ( announcement != <span class="predefined-constant">null</span> ) {
+ tvTitle.setText(announcement.getTitle());
+ tvTitle.setOnClickListener(<span class="keyword">new</span> AnnouncementClickListener(announcement));
+ }
+ <span class="keyword">return</span> convertView;
+ }
+
+ <span class="directive">private</span> <span class="type">class</span> <span class="class">AnnouncementClickListener</span> <span class="directive">implements</span> <span class="predefined-type">View</span>.OnClickListener {
+ <span class="directive">private</span> Announcement announcement;
+
+ AnnouncementClickListener(Announcement announcement) {
+ <span class="local-variable">this</span>.announcement = announcement;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onClick(<span class="predefined-type">View</span> view) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementTapped(announcement);
+ }
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">AnnouncementAdapterDelegate</span> {
+ <span class="type">void</span> onAnnouncementTapped(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/item_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/item_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag"><LinearLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span> <span class="tag">></span>
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span><span class="tag">/></span>
+<span class="tag"></LinearLayout></span></code></pre>
</div>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/v2Android.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/v2Android.html
index 9461ea16877..9a1b2e96206 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/v2Android.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/pages/v2Android.html
@@ -96,7 +96,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">2.0</span><span class="delimiter">"</span></span>;
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -105,7 +111,39 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Long</span>, <span class="predefined-type">Void</span>, Announcement> {
+ <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> TAG = RetrieveAnnouncementTask.class.getSimpleName();
+ AnnouncementFetcher fetcher = <span class="keyword">new</span> AnnouncementFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementTask(RetrieveAnnouncementDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> Announcement doInBackground(<span class="predefined-type">Long</span>... ids) {
+ <span class="keyword">if</span> ( ids != <span class="predefined-constant">null</span> && ids.length >= <span class="integer">1</span>) {
+ <span class="predefined-type">Long</span> announcementId = ids[<span class="integer">0</span>];
+ <span class="keyword">return</span> fetcher.fetchAnnouncement(announcementId);
+ }
+
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>;
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(Announcement announcement) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementFetched(announcement);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -114,7 +152,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementDelegate</span> {
+ <span class="type">void</span> onAnnouncementFetched(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -123,7 +167,49 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity <span class="directive">implements</span> RetrieveAnnouncementDelegate {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">Long</span> announcementId = intent.getLongExtra(MainActivity.EXTRA_ID, <span class="integer">0L</span>);
+ <span class="keyword">new</span> RetrieveAnnouncementTask(<span class="local-variable">this</span>).execute(announcementId);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementFetched(Announcement announcement) {
+ populateUiWithTitleAndBody(announcement.getTitle(), announcement.getBody());
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/single.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/single.html
index 87898282de4..fbb66ed64cd 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/single.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/single.html
@@ -877,7 +877,13 @@ dependency is added when we create an app with the <em>rest-api</em> profile.</p
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>; <i class="conum" data-value="1"></i><b>(1)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>; <i class="conum" data-value="2"></i><b>(2)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">1.0</span><span class="delimiter">"</span></span>; <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -916,7 +922,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/model/Announcement.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/model/Announcement.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network.model</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Announcement</span> {
+ <span class="directive">private</span> <span class="predefined-type">Long</span> id;
+ <span class="directive">private</span> <span class="predefined-type">String</span> title;
+ <span class="directive">private</span> <span class="predefined-type">String</span> body;
+
+ <span class="directive">public</span> <span class="predefined-type">Long</span> getId() {
+ <span class="keyword">return</span> id;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setId(<span class="predefined-type">Long</span> id) {
+ <span class="local-variable">this</span>.id = id;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getTitle() {
+ <span class="keyword">return</span> title;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setTitle(<span class="predefined-type">String</span> title) {
+ <span class="local-variable">this</span>.title = title;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getBody() {
+ <span class="keyword">return</span> body;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setBody(<span class="predefined-type">String</span> body) {
+ <span class="local-variable">this</span>.body = body;
+ }
+}</code></pre>
</div>
</div>
</div>
@@ -928,7 +964,7 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><uses-permission</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">android.permission.INTERNET</span><span class="delimiter">"</span></span><span class="tag">/></span></code></pre>
</div>
</div>
<div class="sect3">
@@ -943,7 +979,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.squareup.okhttp3:okhttp:4.2.2</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
</div>
@@ -965,7 +1001,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.google.code.gson:gson:2.8.6</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -975,7 +1011,18 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/NetworkTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/NetworkTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+
+<span class="type">class</span> <span class="class">NetworkTask</span> {
+ <span class="directive">static</span> Request requestWithUrl(<span class="predefined-type">String</span> url) {
+ <span class="keyword">return</span> <span class="keyword">new</span> Request.Builder()
+ .url(url)
+ .header(<span class="string"><span class="delimiter">"</span><span class="content">Accept-Version</span><span class="delimiter">"</span></span>, Constants.ACCEPT_VERSION)
+ .build();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -984,7 +1031,49 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">android.util.Log</span>;
+
+<span class="keyword">import</span> <span class="include">com.google.gson.Gson</span>;
+<span class="keyword">import</span> <span class="include">com.google.gson.reflect.TypeToken</span>;
+
+<span class="keyword">import</span> <span class="include">java.io.IOException</span>;
+<span class="keyword">import</span> <span class="include">java.lang.reflect.Type</span>;
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.OkHttpClient</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Response</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementsFetcher</span> {
+
+ <span class="directive">private</span> <span class="directive">final</span> <span class="directive">static</span> <span class="predefined-type">String</span> TAG = AnnouncementsFetcher.class.getSimpleName();
+
+ <span class="directive">private</span> OkHttpClient client = <span class="keyword">new</span> OkHttpClient();
+ <span class="directive">private</span> <span class="directive">final</span> Gson gson = <span class="keyword">new</span> Gson();
+
+ <span class="directive">public</span> <span class="predefined-type">List</span><Announcement> fetchAnnouncements() {
+ <span class="predefined-type">Type</span> listType = <span class="keyword">new</span> TypeToken<<span class="predefined-type">List</span><Announcement>>() {}.getType();
+ <span class="keyword">try</span> {
+ <span class="predefined-type">String</span> url = Constants.GRAILS_APP_URL + Constants.ANNOUNCEMENTS_PATH;
+ <span class="predefined-type">String</span> jsonString = fetchAnnouncementsJsonString(url);
+ <span class="keyword">return</span> gson.fromJson(jsonString, listType);
+
+ } <span class="keyword">catch</span> (<span class="exception">IOException</span> e) {
+ Log.e(TAG, e.toString());
+ <span class="keyword">return</span> <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><>();
+ }
+ }
+
+ <span class="directive">private</span> <span class="predefined-type">String</span> fetchAnnouncementsJsonString(<span class="predefined-type">String</span> url) <span class="directive">throws</span> <span class="exception">IOException</span> {
+ Request request = NetworkTask.requestWithUrl(url);
+ Response response = client.newCall(request).execute();
+ <span class="keyword">return</span> response.body().string();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -994,7 +1083,36 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementsFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementsTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Void</span>, <span class="predefined-type">Void</span>, <span class="predefined-type">List</span><Announcement>> {
+
+ <span class="directive">private</span> AnnouncementsFetcher fetcher = <span class="keyword">new</span> AnnouncementsFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementsDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementsTask(RetrieveAnnouncementsDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="predefined-type">List</span><Announcement> doInBackground(<span class="predefined-type">Void</span>... voids) {
+ <span class="keyword">return</span> fetcher.fetchAnnouncements();
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(<span class="predefined-type">List</span><Announcement> announcements) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementsFetched(announcements);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1003,7 +1121,15 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementsDelegate</span> {
+ <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1012,7 +1138,62 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/MainActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/android/activities/MainActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ListView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.adapters.AnnouncementAdapter</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementsTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">MainActivity</span> <span class="directive">extends</span> Activity
+ <span class="directive">implements</span> RetrieveAnnouncementsDelegate, AnnouncementAdapterDelegate {
+
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_ID = <span class="string"><span class="delimiter">"</span><span class="content">id</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_TITLE = <span class="string"><span class="delimiter">"</span><span class="content">title</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_BODY = <span class="string"><span class="delimiter">"</span><span class="content">body</span><span class="delimiter">"</span></span>;
+ <span class="directive">private</span> AnnouncementAdapter adapter;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ <span class="predefined-type">ListView</span> announcementsListView = (<span class="predefined-type">ListView</span>) findViewById(R.id.announcementsListView);
+ adapter = <span class="keyword">new</span> AnnouncementAdapter(<span class="local-variable">this</span>, <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><Announcement>(), <span class="local-variable">this</span>);
+ announcementsListView.setAdapter(adapter);
+
+ <span class="keyword">new</span> RetrieveAnnouncementsTask(<span class="local-variable">this</span>).execute(); <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements) { <i class="conum" data-value="2"></i><b>(2)</b>
+ adapter.clear();
+ adapter.addAll(announcements);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementTapped(Announcement announcement) {
+ segueToAnnouncementActivity(announcement);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> segueToAnnouncementActivity(Announcement announcement) {
+ Intent i = <span class="keyword">new</span> Intent(<span class="local-variable">this</span>, AnnouncementActivity.class);
+ i.putExtra(EXTRA_ID, announcement.getId());
+ i.putExtra(EXTRA_TITLE, announcement.getTitle());
+ i.putExtra(EXTRA_BODY, announcement.getBody());
+ startActivity(i);
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1033,7 +1214,25 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_main.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_main.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><ListView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/announcementsListView</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span><span class="tag">></span>
+ <span class="tag"></ListView></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -1042,19 +1241,86 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.adapters</span>;
+
+<span class="keyword">import</span> <span class="include">android.content.Context</span>;
+<span class="keyword">import</span> <span class="include">android.view.LayoutInflater</span>;
+<span class="keyword">import</span> <span class="include">android.view.View</span>;
+<span class="keyword">import</span> <span class="include">android.view.ViewGroup</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ArrayAdapter</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">androidx.annotation.NonNull</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementAdapter</span> <span class="directive">extends</span> ArrayAdapter<Announcement> {
+ <span class="directive">private</span> AnnouncementAdapterDelegate delegate;
+
+ <span class="directive">public</span> AnnouncementAdapter(<span class="predefined-type">Context</span> context, <span class="predefined-type">List</span><Announcement> announcements, AnnouncementAdapterDelegate delegate) {
+ <span class="local-variable">super</span>(context, <span class="integer">0</span>, announcements);
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@NonNull</span>
+ <span class="directive">public</span> <span class="predefined-type">View</span> getView(<span class="type">int</span> position, <span class="predefined-type">View</span> convertView, <span class="annotation">@NonNull</span> ViewGroup parent) {
+ <span class="keyword">if</span> (convertView == <span class="predefined-constant">null</span>) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_announcement, parent, <span class="predefined-constant">false</span>);
+ }
+ TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
+ Announcement announcement = getItem(position);
+ <span class="keyword">if</span> ( announcement != <span class="predefined-constant">null</span> ) {
+ tvTitle.setText(announcement.getTitle());
+ tvTitle.setOnClickListener(<span class="keyword">new</span> AnnouncementClickListener(announcement));
+ }
+ <span class="keyword">return</span> convertView;
+ }
+
+ <span class="directive">private</span> <span class="type">class</span> <span class="class">AnnouncementClickListener</span> <span class="directive">implements</span> <span class="predefined-type">View</span>.OnClickListener {
+ <span class="directive">private</span> Announcement announcement;
+
+ AnnouncementClickListener(Announcement announcement) {
+ <span class="local-variable">this</span>.announcement = announcement;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onClick(<span class="predefined-type">View</span> view) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementTapped(announcement);
+ }
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">AnnouncementAdapterDelegate</span> {
+ <span class="type">void</span> onAnnouncementTapped(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/item_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/item_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag"><LinearLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span> <span class="tag">></span>
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span><span class="tag">/></span>
+<span class="tag"></LinearLayout></span></code></pre>
</div>
</div>
</div>
@@ -1084,19 +1350,82 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><activity</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">.android.activities.AnnouncementActivity</span><span class="delimiter">"</span></span> <span class="tag">/></span></code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+ populateUi();
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUi() {
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">String</span> title = intent.getStringExtra(MainActivity.EXTRA_TITLE);
+ <span class="directive">final</span> <span class="predefined-type">String</span> body = intent.getStringExtra(MainActivity.EXTRA_BODY);
+ populateUiWithTitleAndBody(title, body);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:text</span>=<span class="string"><span class="delimiter">"</span><span class="content">Announcement Title</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:textSize</span>=<span class="string"><span class="delimiter">"</span><span class="content">20dp</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+ <span class="tag"><WebView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/wvBody</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_below</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
@@ -1340,7 +1669,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">2.0</span><span class="delimiter">"</span></span>;
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1349,7 +1684,39 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Long</span>, <span class="predefined-type">Void</span>, Announcement> {
+ <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> TAG = RetrieveAnnouncementTask.class.getSimpleName();
+ AnnouncementFetcher fetcher = <span class="keyword">new</span> AnnouncementFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementTask(RetrieveAnnouncementDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> Announcement doInBackground(<span class="predefined-type">Long</span>... ids) {
+ <span class="keyword">if</span> ( ids != <span class="predefined-constant">null</span> && ids.length >= <span class="integer">1</span>) {
+ <span class="predefined-type">Long</span> announcementId = ids[<span class="integer">0</span>];
+ <span class="keyword">return</span> fetcher.fetchAnnouncement(announcementId);
+ }
+
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>;
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(Announcement announcement) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementFetched(announcement);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1358,7 +1725,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementDelegate</span> {
+ <span class="type">void</span> onAnnouncementFetched(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1367,7 +1740,49 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity <span class="directive">implements</span> RetrieveAnnouncementDelegate {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">Long</span> announcementId = intent.getLongExtra(MainActivity.EXTRA_ID, <span class="integer">0L</span>);
+ <span class="keyword">new</span> RetrieveAnnouncementTask(<span class="local-variable">this</span>).execute(announcementId);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementFetched(Announcement announcement) {
+ populateUiWithTitleAndBody(announcement.getTitle(), announcement.getBody());
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/version2.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/version2.html
index ad66459f56a..178de4d67a0 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/version2.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/version2.html
@@ -334,7 +334,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>;
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">2.0</span><span class="delimiter">"</span></span>;
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -343,7 +349,39 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Long</span>, <span class="predefined-type">Void</span>, Announcement> {
+ <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> TAG = RetrieveAnnouncementTask.class.getSimpleName();
+ AnnouncementFetcher fetcher = <span class="keyword">new</span> AnnouncementFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementTask(RetrieveAnnouncementDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> Announcement doInBackground(<span class="predefined-type">Long</span>... ids) {
+ <span class="keyword">if</span> ( ids != <span class="predefined-constant">null</span> && ids.length >= <span class="integer">1</span>) {
+ <span class="predefined-type">Long</span> announcementId = ids[<span class="integer">0</span>];
+ <span class="keyword">return</span> fetcher.fetchAnnouncement(announcementId);
+ }
+
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>;
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(Announcement announcement) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementFetched(announcement);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -352,7 +390,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementDelegate.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementDelegate</span> {
+ <span class="type">void</span> onAnnouncementFetched(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -361,7 +405,49 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v2/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity <span class="directive">implements</span> RetrieveAnnouncementDelegate {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">Long</span> announcementId = intent.getLongExtra(MainActivity.EXTRA_ID, <span class="integer">0L</span>);
+ <span class="keyword">new</span> RetrieveAnnouncementTask(<span class="local-variable">this</span>).execute(announcementId);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementFetched(Announcement announcement) {
+ populateUiWithTitleAndBody(announcement.getTitle(), announcement.getBody());
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/writingTheAndroidApp.html b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/writingTheAndroidApp.html
index 7553f77ead9..69ee1809355 100644
--- a/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/writingTheAndroidApp.html
+++ b/guides/building-an-android-client-powered-by-a-grails-backend/4/guide/writingTheAndroidApp.html
@@ -141,7 +141,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/Constants.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/Constants.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Constants</span> {
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> GRAILS_APP_URL = <span class="string"><span class="delimiter">"</span><span class="content">http://192.168.1.42:8080/</span><span class="delimiter">"</span></span>; <i class="conum" data-value="1"></i><b>(1)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ANNOUNCEMENTS_PATH = <span class="string"><span class="delimiter">"</span><span class="content">announcements</span><span class="delimiter">"</span></span>; <i class="conum" data-value="2"></i><b>(2)</b>
+ <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> ACCEPT_VERSION = <span class="string"><span class="delimiter">"</span><span class="content">1.0</span><span class="delimiter">"</span></span>; <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -180,7 +186,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/model/Announcement.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/model/Announcement.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network.model</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">Announcement</span> {
+ <span class="directive">private</span> <span class="predefined-type">Long</span> id;
+ <span class="directive">private</span> <span class="predefined-type">String</span> title;
+ <span class="directive">private</span> <span class="predefined-type">String</span> body;
+
+ <span class="directive">public</span> <span class="predefined-type">Long</span> getId() {
+ <span class="keyword">return</span> id;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setId(<span class="predefined-type">Long</span> id) {
+ <span class="local-variable">this</span>.id = id;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getTitle() {
+ <span class="keyword">return</span> title;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setTitle(<span class="predefined-type">String</span> title) {
+ <span class="local-variable">this</span>.title = title;
+ }
+
+ <span class="directive">public</span> <span class="predefined-type">String</span> getBody() {
+ <span class="keyword">return</span> body;
+ }
+
+ <span class="directive">public</span> <span class="type">void</span> setBody(<span class="predefined-type">String</span> body) {
+ <span class="local-variable">this</span>.body = body;
+ }
+}</code></pre>
</div>
</div>
</div>
@@ -192,7 +228,7 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><uses-permission</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">android.permission.INTERNET</span><span class="delimiter">"</span></span><span class="tag">/></span></code></pre>
</div>
</div>
<div class="sect3">
@@ -207,7 +243,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.squareup.okhttp3:okhttp:4.2.2</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
</div>
@@ -229,7 +265,7 @@ dependencies block, the next line:</p>
<div class="listingblock">
<div class="title">/app/build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:{sourcedir}/..<span class="regexp"><span class="delimiter">/</span><span class="content">complete-android-v1</span><span class="delimiter">/</span></span>app/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"> implementation <span class="string"><span class="delimiter">'</span><span class="content">com.google.code.gson:gson:2.8.6</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -239,7 +275,18 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/NetworkTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/NetworkTask.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+
+<span class="type">class</span> <span class="class">NetworkTask</span> {
+ <span class="directive">static</span> Request requestWithUrl(<span class="predefined-type">String</span> url) {
+ <span class="keyword">return</span> <span class="keyword">new</span> Request.Builder()
+ .url(url)
+ .header(<span class="string"><span class="delimiter">"</span><span class="content">Accept-Version</span><span class="delimiter">"</span></span>, Constants.ACCEPT_VERSION)
+ .build();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -248,7 +295,49 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/network/AnnouncementsFetcher.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.network</span>;
+
+<span class="keyword">import</span> <span class="include">android.util.Log</span>;
+
+<span class="keyword">import</span> <span class="include">com.google.gson.Gson</span>;
+<span class="keyword">import</span> <span class="include">com.google.gson.reflect.TypeToken</span>;
+
+<span class="keyword">import</span> <span class="include">java.io.IOException</span>;
+<span class="keyword">import</span> <span class="include">java.lang.reflect.Type</span>;
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.OkHttpClient</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Request</span>;
+<span class="keyword">import</span> <span class="include">okhttp3.Response</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementsFetcher</span> {
+
+ <span class="directive">private</span> <span class="directive">final</span> <span class="directive">static</span> <span class="predefined-type">String</span> TAG = AnnouncementsFetcher.class.getSimpleName();
+
+ <span class="directive">private</span> OkHttpClient client = <span class="keyword">new</span> OkHttpClient();
+ <span class="directive">private</span> <span class="directive">final</span> Gson gson = <span class="keyword">new</span> Gson();
+
+ <span class="directive">public</span> <span class="predefined-type">List</span><Announcement> fetchAnnouncements() {
+ <span class="predefined-type">Type</span> listType = <span class="keyword">new</span> TypeToken<<span class="predefined-type">List</span><Announcement>>() {}.getType();
+ <span class="keyword">try</span> {
+ <span class="predefined-type">String</span> url = Constants.GRAILS_APP_URL + Constants.ANNOUNCEMENTS_PATH;
+ <span class="predefined-type">String</span> jsonString = fetchAnnouncementsJsonString(url);
+ <span class="keyword">return</span> gson.fromJson(jsonString, listType);
+
+ } <span class="keyword">catch</span> (<span class="exception">IOException</span> e) {
+ Log.e(TAG, e.toString());
+ <span class="keyword">return</span> <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><>();
+ }
+ }
+
+ <span class="directive">private</span> <span class="predefined-type">String</span> fetchAnnouncementsJsonString(<span class="predefined-type">String</span> url) <span class="directive">throws</span> <span class="exception">IOException</span> {
+ Request request = NetworkTask.requestWithUrl(url);
+ Response response = client.newCall(request).execute();
+ <span class="keyword">return</span> response.body().string();
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -258,7 +347,36 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/asynctasks/RetrieveAnnouncementsTask.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.asynctasks</span>;
+
+<span class="keyword">import</span> <span class="include">android.os.AsyncTask</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.AnnouncementsFetcher</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">RetrieveAnnouncementsTask</span> <span class="directive">extends</span> AsyncTask<<span class="predefined-type">Void</span>, <span class="predefined-type">Void</span>, <span class="predefined-type">List</span><Announcement>> {
+
+ <span class="directive">private</span> AnnouncementsFetcher fetcher = <span class="keyword">new</span> AnnouncementsFetcher();
+ <span class="directive">private</span> RetrieveAnnouncementsDelegate delegate;
+
+ <span class="directive">public</span> RetrieveAnnouncementsTask(RetrieveAnnouncementsDelegate delegate) {
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="predefined-type">List</span><Announcement> doInBackground(<span class="predefined-type">Void</span>... voids) {
+ <span class="keyword">return</span> fetcher.fetchAnnouncements();
+ }
+
+ <span class="directive">protected</span> <span class="type">void</span> onPostExecute(<span class="predefined-type">List</span><Announcement> announcements) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementsFetched(announcements);
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -267,7 +385,15 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/RetrieveAnnouncementsDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">RetrieveAnnouncementsDelegate</span> {
+ <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements);
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -276,7 +402,62 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/MainActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1/app/src/main/java/intranet/client/android/activities/MainActivity.java[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ListView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.ArrayList</span>;
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.adapters.AnnouncementAdapter</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.asynctasks.RetrieveAnnouncementsTask</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.RetrieveAnnouncementsDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">MainActivity</span> <span class="directive">extends</span> Activity
+ <span class="directive">implements</span> RetrieveAnnouncementsDelegate, AnnouncementAdapterDelegate {
+
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_ID = <span class="string"><span class="delimiter">"</span><span class="content">id</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_TITLE = <span class="string"><span class="delimiter">"</span><span class="content">title</span><span class="delimiter">"</span></span>;
+ <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> EXTRA_BODY = <span class="string"><span class="delimiter">"</span><span class="content">body</span><span class="delimiter">"</span></span>;
+ <span class="directive">private</span> AnnouncementAdapter adapter;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ <span class="predefined-type">ListView</span> announcementsListView = (<span class="predefined-type">ListView</span>) findViewById(R.id.announcementsListView);
+ adapter = <span class="keyword">new</span> AnnouncementAdapter(<span class="local-variable">this</span>, <span class="keyword">new</span> <span class="predefined-type">ArrayList</span><Announcement>(), <span class="local-variable">this</span>);
+ announcementsListView.setAdapter(adapter);
+
+ <span class="keyword">new</span> RetrieveAnnouncementsTask(<span class="local-variable">this</span>).execute(); <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementsFetched(<span class="predefined-type">List</span><Announcement> announcements) { <i class="conum" data-value="2"></i><b>(2)</b>
+ adapter.clear();
+ adapter.addAll(announcements);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onAnnouncementTapped(Announcement announcement) {
+ segueToAnnouncementActivity(announcement);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> segueToAnnouncementActivity(Announcement announcement) {
+ Intent i = <span class="keyword">new</span> Intent(<span class="local-variable">this</span>, AnnouncementActivity.class);
+ i.putExtra(EXTRA_ID, announcement.getId());
+ i.putExtra(EXTRA_TITLE, announcement.getTitle());
+ i.putExtra(EXTRA_BODY, announcement.getBody());
+ startActivity(i);
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -297,7 +478,25 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_main.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_main.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><ListView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/announcementsListView</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span><span class="tag">></span>
+ <span class="tag"></ListView></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -306,19 +505,86 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/adapters/AnnouncementAdapter.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.adapters</span>;
+
+<span class="keyword">import</span> <span class="include">android.content.Context</span>;
+<span class="keyword">import</span> <span class="include">android.view.LayoutInflater</span>;
+<span class="keyword">import</span> <span class="include">android.view.View</span>;
+<span class="keyword">import</span> <span class="include">android.view.ViewGroup</span>;
+<span class="keyword">import</span> <span class="include">android.widget.ArrayAdapter</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">java.util.List</span>;
+
+<span class="keyword">import</span> <span class="include">androidx.annotation.NonNull</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.android.delegates.AnnouncementAdapterDelegate</span>;
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementAdapter</span> <span class="directive">extends</span> ArrayAdapter<Announcement> {
+ <span class="directive">private</span> AnnouncementAdapterDelegate delegate;
+
+ <span class="directive">public</span> AnnouncementAdapter(<span class="predefined-type">Context</span> context, <span class="predefined-type">List</span><Announcement> announcements, AnnouncementAdapterDelegate delegate) {
+ <span class="local-variable">super</span>(context, <span class="integer">0</span>, announcements);
+ <span class="local-variable">this</span>.delegate = delegate;
+ }
+
+ <span class="annotation">@NonNull</span>
+ <span class="directive">public</span> <span class="predefined-type">View</span> getView(<span class="type">int</span> position, <span class="predefined-type">View</span> convertView, <span class="annotation">@NonNull</span> ViewGroup parent) {
+ <span class="keyword">if</span> (convertView == <span class="predefined-constant">null</span>) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_announcement, parent, <span class="predefined-constant">false</span>);
+ }
+ TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
+ Announcement announcement = getItem(position);
+ <span class="keyword">if</span> ( announcement != <span class="predefined-constant">null</span> ) {
+ tvTitle.setText(announcement.getTitle());
+ tvTitle.setOnClickListener(<span class="keyword">new</span> AnnouncementClickListener(announcement));
+ }
+ <span class="keyword">return</span> convertView;
+ }
+
+ <span class="directive">private</span> <span class="type">class</span> <span class="class">AnnouncementClickListener</span> <span class="directive">implements</span> <span class="predefined-type">View</span>.OnClickListener {
+ <span class="directive">private</span> Announcement announcement;
+
+ AnnouncementClickListener(Announcement announcement) {
+ <span class="local-variable">this</span>.announcement = announcement;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span class="type">void</span> onClick(<span class="predefined-type">View</span> view) {
+ <span class="keyword">if</span> ( delegate != <span class="predefined-constant">null</span> ) {
+ delegate.onAnnouncementTapped(announcement);
+ }
+ }
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/delegates/AnnouncementAdapterDelegate.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.delegates</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.network.model.Announcement</span>;
+
+<span class="directive">public</span> <span class="type">interface</span> <span class="class">AnnouncementAdapterDelegate</span> {
+ <span class="type">void</span> onAnnouncementTapped(Announcement announcement);
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/item_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/item_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag"><LinearLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span> <span class="tag">></span>
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span><span class="tag">/></span>
+<span class="tag"></LinearLayout></span></code></pre>
</div>
</div>
</div>
@@ -348,19 +614,82 @@ always set with the value defined in the <em>Constants.java</em> class</p>
<div class="listingblock">
<div class="title">/app/src/main/AndroidManifest.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/AndroidManifest.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"> <span class="tag"><activity</span> <span class="attribute-name">android:name</span>=<span class="string"><span class="delimiter">"</span><span class="content">.android.activities.AnnouncementActivity</span><span class="delimiter">"</span></span> <span class="tag">/></span></code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java">link:{sourcedir}/../complete-android-v1<span class="comment">//app/src/main/java/intranet/client/android/activities/AnnouncementActivity.java[role=include]</span></code></pre>
+<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">intranet.client.android.activities</span>;
+
+<span class="keyword">import</span> <span class="include">android.app.Activity</span>;
+<span class="keyword">import</span> <span class="include">android.content.Intent</span>;
+<span class="keyword">import</span> <span class="include">android.os.Bundle</span>;
+<span class="keyword">import</span> <span class="include">android.webkit.WebView</span>;
+<span class="keyword">import</span> <span class="include">android.widget.TextView</span>;
+
+<span class="keyword">import</span> <span class="include">intranet.client.R</span>;
+
+<span class="directive">public</span> <span class="type">class</span> <span class="class">AnnouncementActivity</span> <span class="directive">extends</span> Activity {
+
+ <span class="directive">private</span> TextView tvTitle;
+ <span class="directive">private</span> WebView wvBody;
+
+ <span class="annotation">@Override</span>
+ <span class="directive">protected</span> <span class="type">void</span> onCreate(Bundle savedInstanceState) {
+ <span class="local-variable">super</span>.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_announcement);
+
+ tvTitle = (TextView) findViewById(R.id.tvTitle);
+ wvBody = (WebView) findViewById(R.id.wvBody);
+ populateUi();
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUi() {
+ Intent intent = getIntent();
+ <span class="directive">final</span> <span class="predefined-type">String</span> title = intent.getStringExtra(MainActivity.EXTRA_TITLE);
+ <span class="directive">final</span> <span class="predefined-type">String</span> body = intent.getStringExtra(MainActivity.EXTRA_BODY);
+ populateUiWithTitleAndBody(title, body);
+ }
+
+ <span class="directive">private</span> <span class="type">void</span> populateUiWithTitleAndBody(<span class="directive">final</span> <span class="predefined-type">String</span> title, <span class="directive">final</span> <span class="predefined-type">String</span> body) {
+ tvTitle.setText(title);
+ <span class="directive">final</span> <span class="predefined-type">String</span> mime = <span class="string"><span class="delimiter">"</span><span class="content">text/html</span><span class="delimiter">"</span></span>;
+ <span class="directive">final</span> <span class="predefined-type">String</span> encoding = <span class="string"><span class="delimiter">"</span><span class="content">utf-8</span><span class="delimiter">"</span></span>;
+ wvBody.loadDataWithBaseURL(<span class="predefined-constant">null</span>, body, mime, encoding, <span class="predefined-constant">null</span>);
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/app/src/main/res/layout/activity_announcement.xml</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="xml">link:{sourcedir}/../complete-android-v1/app/src/main/res/layout/activity_announcement.xml[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="xml"><span class="preprocessor"><?xml version="1.0" encoding="utf-8"?></span>
+<span class="tag"><RelativeLayout</span> <span class="attribute-name">xmlns:android</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/apk/res/android</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">xmlns:tools</span>=<span class="string"><span class="delimiter">"</span><span class="content">http://schemas.android.com/tools</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/activity_main</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingBottom</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingLeft</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingRight</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_horizontal_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:paddingTop</span>=<span class="string"><span class="delimiter">"</span><span class="content">@dimen/activity_vertical_margin</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:context</span>=<span class="string"><span class="delimiter">"</span><span class="content">grails.company.client.intranet.client.MainActivity</span><span class="delimiter">"</span></span><span class="tag">></span>
+
+ <span class="tag"><TextView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">tools:text</span>=<span class="string"><span class="delimiter">"</span><span class="content">Announcement Title</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:textSize</span>=<span class="string"><span class="delimiter">"</span><span class="content">20dp</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">match_parent</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+ <span class="tag"><WebView</span>
+ <span class="attribute-name">android:id</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/wvBody</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_below</span>=<span class="string"><span class="delimiter">"</span><span class="content">@+id/tvTitle</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_width</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span>
+ <span class="attribute-name">android:layout_height</span>=<span class="string"><span class="delimiter">"</span><span class="content">wrap_content</span><span class="delimiter">"</span></span> <span class="tag">/></span>
+
+<span class="tag"></RelativeLayout></span></code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/index.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/index.html
index 8293b428ea6..4dc15516aab 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/index.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/index.html
@@ -887,13 +887,25 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface Announcement : NSObject
+
+@property (nonatomic, copy)NSNumber *primaryKey;
+@property (nonatomic, copy)NSString *title;
+@property (nonatomic, copy)NSString *body;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "Announcement.h"
+
+@implementation Announcement
+
+@end</code></pre>
</div>
</div>
@@ -913,25 +925,255 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+#import "ElementBuilder.h"
+
+extern NSString *kAnnouncementBuilderErrorDomain;
+
+enum {
+ kAnnouncementBuilderInvalidJSONError,
+ kAnnouncementBuilderMissingDataError,
+};
+
+@interface AnnouncementBuilder : ElementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementBuilder.h"
+#import "Announcement.h"
+
+static NSString *kJSONKeyId = @"id";
+static NSString *kJSONKeyTitle = @"title";
+static NSString *kJSONKeyBody = @"body";
+
+@implementation AnnouncementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error {
+
+ return [super arrayFromJSON:objectNotation
+ key:nil
+ error:error
+ invalidJSONErrorCode:kAnnouncementBuilderInvalidJSONError
+ missingDataErrorCode:kAnnouncementBuilderMissingDataError
+ errorDomain:kAnnouncementBuilderErrorDomain];
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ Announcement *announcement = [[Announcement alloc] init];
+
+ if([[dict objectForKey:kJSONKeyId] isKindOfClass:[NSNumber class]]) {
+ announcement.primaryKey = (NSNumber *)[dict objectForKey:kJSONKeyId];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyTitle] isKindOfClass:[NSString class]]) {
+ announcement.title = (NSString *)[dict objectForKey:kJSONKeyTitle];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyBody] isKindOfClass:[NSString class]]) {
+ announcement.body = (NSString *)[dict objectForKey:kJSONKeyBody];
+ }
+
+ return announcement;
+
+}
+
+- ( NSError *)invalidJsonError {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ return [NSError errorWithDomain:kAnnouncementBuilderErrorDomain
+ code:kAnnouncementBuilderInvalidJSONError
+ userInfo:userInfo];
+}
+
+@end
+
+NSString *kAnnouncementBuilderErrorDomain = @"kAnnouncementBuilderErrorDomain";</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface ElementBuilder : NSObject
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain;
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "ElementBuilder.h"
+
+@implementation ElementBuilder
+
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ id parsedObject = [self parseJSON:objectNotation
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ errorDomain:errorDomain];
+
+ NSArray *elements = nil;
+ if ( [parsedObject isKindOfClass:[NSDictionary class]]) {
+ elements = [((NSDictionary *)parsedObject) objectForKey:key];
+
+ } else if ( [parsedObject isKindOfClass:[NSArray class]] ) {
+ elements = (NSArray *)parsedObject;
+ }
+
+ if (elements == nil) {
+ if (error != NULL) {
+ *error = [NSError errorWithDomain:errorDomain code:missingDataErrorCode userInfo:nil];
+ }
+ return nil;
+ }
+
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:[elements count]];
+ for (NSDictionary *parsedEl in elements) {
+ id el = [self newElementWithDictionary:parsedEl
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ missingDataErrorCode:missingDataErrorCode
+ errorDomain:errorDomain];
+ // Return nil becuase there has been an error in the previous method call
+ if(!el) {
+ return nil;
+ }
+
+ if([self isElementValid:el]) {
+ [results addObject:el];
+ }
+ }
+ return [results copy];
+}
+
+
+- (BOOL)isElementValid:(id)el {
+ // This may be overriden in a subclass
+ return YES;
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
+}
+
+
+- (id)parseJSON:(NSString *)objectNotation
+ error:(NSError **)error
+invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ NSParameterAssert(objectNotation != nil);
+ id jsonObject;
+
+ NSError *localError = nil;
+ if(objectNotation != nil) {
+ NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
+ jsonObject = [NSJSONSerialization JSONObjectWithData:unicodeNotation
+ options:0
+ error:&localError];
+ }
+ if (jsonObject == nil) {
+ if (error != NULL) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ if (localError != nil) {
+ [userInfo setObject:localError forKey:NSUnderlyingErrorKey];
+ }
+ *error = [NSError errorWithDomain:errorDomain code:invalidJSONErrorCode userInfo:userInfo];
+ }
+ return nil;
+ }
+ return jsonObject;
+}
+
++ (id)objectOrNull:(id)object {
+ if (!object || object == [NSNull null]) return nil;
+ return object;
+}
+
++ (BOOL)isNotNilAndNotArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isArrayTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isNumberTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+
+ return [self isNotNilTheKey:key atDict:dict] && ![self isStringTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSString class]]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSNumber class]]) {
+ return YES;
+ }
+ return NO;
+}
++ (BOOL)isArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSArray class]]) {
+ return YES;
+ }
+ return NO;
+}
+
+@end</code></pre>
</div>
</div>
@@ -970,7 +1212,22 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"1.0";
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1004,7 +1261,52 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "GrailsFetcher.h"
+
+@interface GrailsFetcher () <NSURLSessionDelegate>
+
+@end
+
+@implementation GrailsFetcher
+
+#pragma mark - LifeCycle
+
+- (id)init {
+ if(self = [super init]) {
+ NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ self.session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ }
+ return self;
+}
+
+
+#pragma mark - Public
+
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval {
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+ [urlRequest setHTTPMethod:@"GET"];
+
+ [[self headers] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
+ [urlRequest setValue:obj forHTTPHeaderField:key];
+ }];
+ return urlRequest;
+}
+
+#pragma mark - Private
+
+- (NSDictionary *)headers {
+ return @{@"Accept-Version": kApiVersion}; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1018,13 +1320,97 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+
+@protocol AnnouncementsFetcherDelegate;
+
+@interface AnnouncementsFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsFetcher.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+
+@interface AnnouncementsFetcher ()
+
+@property ( nonatomic, weak) id<AnnouncementsFetcherDelegate> delegate;
+
+@property ( nonatomic, strong) AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+- (void)fetchAnnouncements {
+
+ [self fetchAnnouncementsWithCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ NSArray *announcements = [self.builder announcementsFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementsFetched:announcements];
+ }
+
+ }];
+}
+
+- (void)fetchAnnouncementsWithCompletionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementsURLRequest] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementsURLRequest {
+ NSString *urlStr = [self announcementsURLString];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementsURLString {
+ return [NSString stringWithFormat:@"%@/%@", kServerUrl, kAnnouncementsResourcePath];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1033,7 +1419,16 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@protocol AnnouncementsFetcherDelegate <NSObject>
+
+- (void)announcementsFetchingFailed;
+
+- (void)announcementsFetched:(NSArray *)announcements;
+
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1042,13 +1437,139 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@class DetailViewController;
+
+@interface MasterViewController : UITableViewController
+
+@property (strong, nonatomic) DetailViewController *detailViewController;
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "MasterViewController.h"
+#import "DetailViewController.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementsFetcher.h"
+#import "AnnouncementsTableViewDataSource.h"
+#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+static NSString *kSegueShowDetail = @"showDetail";
+
+@interface MasterViewController () <AnnouncementsFetcherDelegate>
+
+@property NSMutableArray *objects;
+
+@property ( nonatomic, strong ) AnnouncementsFetcher *fetcher;
+
+@property ( nonatomic, strong) id<UITableViewDataSource> tableViewDataSource;
+@property ( nonatomic, strong) id<UITableViewDelegate> tableViewDelegate;
+
+@end
+
+@implementation MasterViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.tableViewDataSource = [[AnnouncementsTableViewDataSource alloc] init];
+ self.tableViewDelegate = [[AnnouncementsTableViewDelegate alloc] init];
+ self.tableView.dataSource = self.tableViewDataSource;
+ self.tableView.delegate = self.tableViewDelegate;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController.isCollapsed;
+ [super viewWillAppear:animated];
+
+ [self registerNotifications];
+ [self fetchAnnouncements]; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+ [self unregisterNotifications];
+}
+
+#pragma mark - Notifications
+
+-(void)registerNotifications {
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(announcementTapped:) name:kAnnouncementTappedNotification object:nil];
+}
+
+- (void)unregisterNotifications {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self name:kAnnouncementTappedNotification object:nil];
+}
+
+#pragma mark - Segues
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}
+
+#pragma mark - Private Methods
+
+- (void)announcementTapped:(NSNotification *)notification {
+ if([[notification object] isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)[notification object];
+ [self performSegueWithIdentifier:kSegueShowDetail sender:announcement];
+ }
+}
+
+- (void)setNetworkActivityIndicator:(BOOL)visible {
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible];
+}
+
+- (void)fetchAnnouncements {
+ [self setNetworkActivityIndicator:YES];
+ [self.fetcher fetchAnnouncements];
+}
+
+#pragma mark - AnnouncementsFetcherDelegate
+
+- (void)announcementsFetchingFailed {
+ [self setNetworkActivityIndicator:NO];
+}
+
+- (void)announcementsFetched:(NSArray *)announcements {
+ [self setNetworkActivityIndicator:NO];
+
+ if ( [self.tableViewDataSource isKindOfClass:[AnnouncementsTableViewDataSource class]]) {
+ ((AnnouncementsTableViewDataSource *)self.tableViewDataSource).announcements = announcements;
+ }
+ if ( [self.tableViewDelegate isKindOfClass:[AnnouncementsTableViewDelegate class]]) {
+ ((AnnouncementsTableViewDelegate *)self.tableViewDelegate).announcements = announcements;
+ }
+ [self.tableView reloadData]; <i class="conum" data-value="2"></i><b>(2)</b>
+}
+
+#pragma mark - Lazy
+
+- (AnnouncementsFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementsFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1069,25 +1590,87 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@interface AnnouncementsTableViewDataSource : NSObject <UITableViewDataSource>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDataSource.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDataSource
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return [self.announcements count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ cell.textLabel.text = announcement.title;
+ }
+ }
+
+ return cell;
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+static NSString *kAnnouncementTappedNotification = @"AnnouncementTappedNotification";
+
+@interface AnnouncementsTableViewDelegate : NSObject <UITableViewDelegate>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDelegate
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAnnouncementTappedNotification <i class="conum" data-value="1"></i><b>(1)</b>
+ object:announcement
+ userInfo:nil];
+ }
+ }
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1118,7 +1701,17 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="imageblock">
@@ -1140,13 +1733,68 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+
+@interface DetailViewController () <UIWebViewDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+@end</code></pre>
</div>
</div>
@@ -1394,7 +2042,22 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"2.0"; <i class="conum" data-value="1"></i><b>(1)</b>
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1414,7 +2077,17 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcementPrimaryKey = announcement.primaryKey; <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1431,13 +2104,99 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, assign) NSNumber *announcementPrimaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+#import "AnnouncementFetcher.h"
+
+@interface DetailViewController () <UIWebViewDelegate, AnnouncementFetcherDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@property ( nonatomic, strong ) AnnouncementFetcher *fetcher;
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+
+ if ( self.announcementPrimaryKey ) {
+ [self.fetcher fetchAnnouncement:self.announcementPrimaryKey];
+ }
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+#pragma mark - AnnouncementFetcherDelegate
+
+- (void)announcementFetchingFailed {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+- (void)announcementFetched:(Announcement *)announcement {
+ [[self activityIndicatorView] stopAnimating];
+ self.announcement = announcement;
+}
+
+
+#pragma mark - Lazy
+
+- (AnnouncementFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1446,13 +2205,100 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+
+@interface AnnouncementFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+@interface AnnouncementFetcher ()
+
+@property ( nonatomic, weak )id<AnnouncementFetcherDelegate> delegate;
+@property ( nonatomic, strong )AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementFetcher
+
+#pragma mark - LifeCycle
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+#pragma mark - Public Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey {
+
+ [self fetchAnnouncement:primaryKey completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ Announcement *announcement = [self.builder announcementFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementFetched:announcement];
+ }
+ }];
+
+}
+
+#pragma mark - Private Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey completionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementURLRequest:primaryKey] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementURLRequest:(NSNumber *)primaryKey {
+ NSString *urlStr = [self announcementURLString:primaryKey];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementURLString:(NSNumber *)primaryKey {
+ return [NSString stringWithFormat:@"%@/%@/%@", kServerUrl, kAnnouncementsResourcePath, primaryKey];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1461,7 +2307,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+@class Announcement;
+
+@protocol AnnouncementFetcherDelegate <NSObject>
+
+- (void)announcementFetchingFailed;
+
+- (void)announcementFetched:(Announcement *)announcement;
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
index c7b7c24cb39..a40fa6f671f 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
@@ -98,7 +98,17 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="imageblock">
@@ -120,13 +130,68 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+
+@interface DetailViewController () <UIWebViewDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
index 60808000900..7f2718968ca 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
@@ -96,25 +96,255 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+#import "ElementBuilder.h"
+
+extern NSString *kAnnouncementBuilderErrorDomain;
+
+enum {
+ kAnnouncementBuilderInvalidJSONError,
+ kAnnouncementBuilderMissingDataError,
+};
+
+@interface AnnouncementBuilder : ElementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementBuilder.h"
+#import "Announcement.h"
+
+static NSString *kJSONKeyId = @"id";
+static NSString *kJSONKeyTitle = @"title";
+static NSString *kJSONKeyBody = @"body";
+
+@implementation AnnouncementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error {
+
+ return [super arrayFromJSON:objectNotation
+ key:nil
+ error:error
+ invalidJSONErrorCode:kAnnouncementBuilderInvalidJSONError
+ missingDataErrorCode:kAnnouncementBuilderMissingDataError
+ errorDomain:kAnnouncementBuilderErrorDomain];
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ Announcement *announcement = [[Announcement alloc] init];
+
+ if([[dict objectForKey:kJSONKeyId] isKindOfClass:[NSNumber class]]) {
+ announcement.primaryKey = (NSNumber *)[dict objectForKey:kJSONKeyId];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyTitle] isKindOfClass:[NSString class]]) {
+ announcement.title = (NSString *)[dict objectForKey:kJSONKeyTitle];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyBody] isKindOfClass:[NSString class]]) {
+ announcement.body = (NSString *)[dict objectForKey:kJSONKeyBody];
+ }
+
+ return announcement;
+
+}
+
+- ( NSError *)invalidJsonError {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ return [NSError errorWithDomain:kAnnouncementBuilderErrorDomain
+ code:kAnnouncementBuilderInvalidJSONError
+ userInfo:userInfo];
+}
+
+@end
+
+NSString *kAnnouncementBuilderErrorDomain = @"kAnnouncementBuilderErrorDomain";</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface ElementBuilder : NSObject
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain;
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "ElementBuilder.h"
+
+@implementation ElementBuilder
+
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ id parsedObject = [self parseJSON:objectNotation
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ errorDomain:errorDomain];
+
+ NSArray *elements = nil;
+ if ( [parsedObject isKindOfClass:[NSDictionary class]]) {
+ elements = [((NSDictionary *)parsedObject) objectForKey:key];
+
+ } else if ( [parsedObject isKindOfClass:[NSArray class]] ) {
+ elements = (NSArray *)parsedObject;
+ }
+
+ if (elements == nil) {
+ if (error != NULL) {
+ *error = [NSError errorWithDomain:errorDomain code:missingDataErrorCode userInfo:nil];
+ }
+ return nil;
+ }
+
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:[elements count]];
+ for (NSDictionary *parsedEl in elements) {
+ id el = [self newElementWithDictionary:parsedEl
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ missingDataErrorCode:missingDataErrorCode
+ errorDomain:errorDomain];
+ // Return nil becuase there has been an error in the previous method call
+ if(!el) {
+ return nil;
+ }
+
+ if([self isElementValid:el]) {
+ [results addObject:el];
+ }
+ }
+ return [results copy];
+}
+
+
+- (BOOL)isElementValid:(id)el {
+ // This may be overriden in a subclass
+ return YES;
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
+}
+
+
+- (id)parseJSON:(NSString *)objectNotation
+ error:(NSError **)error
+invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ NSParameterAssert(objectNotation != nil);
+ id jsonObject;
+
+ NSError *localError = nil;
+ if(objectNotation != nil) {
+ NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
+ jsonObject = [NSJSONSerialization JSONObjectWithData:unicodeNotation
+ options:0
+ error:&localError];
+ }
+ if (jsonObject == nil) {
+ if (error != NULL) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ if (localError != nil) {
+ [userInfo setObject:localError forKey:NSUnderlyingErrorKey];
+ }
+ *error = [NSError errorWithDomain:errorDomain code:invalidJSONErrorCode userInfo:userInfo];
+ }
+ return nil;
+ }
+ return jsonObject;
+}
+
++ (id)objectOrNull:(id)object {
+ if (!object || object == [NSNull null]) return nil;
+ return object;
+}
+
++ (BOOL)isNotNilAndNotArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isArrayTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isNumberTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+
+ return [self isNotNilTheKey:key atDict:dict] && ![self isStringTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSString class]]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSNumber class]]) {
+ return YES;
+ }
+ return NO;
+}
++ (BOOL)isArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSArray class]]) {
+ return YES;
+ }
+ return NO;
+}
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
index 474452ff72b..724693eb047 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
@@ -96,13 +96,25 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface Announcement : NSObject
+
+@property (nonatomic, copy)NSNumber *primaryKey;
+@property (nonatomic, copy)NSString *title;
+@property (nonatomic, copy)NSString *body;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "Announcement.h"
+
+@implementation Announcement
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
index 2dddd4e630f..aa301beb772 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
@@ -96,7 +96,22 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"1.0";
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -130,7 +145,52 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "GrailsFetcher.h"
+
+@interface GrailsFetcher () <NSURLSessionDelegate>
+
+@end
+
+@implementation GrailsFetcher
+
+#pragma mark - LifeCycle
+
+- (id)init {
+ if(self = [super init]) {
+ NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ self.session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ }
+ return self;
+}
+
+
+#pragma mark - Public
+
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval {
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+ [urlRequest setHTTPMethod:@"GET"];
+
+ [[self headers] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
+ [urlRequest setValue:obj forHTTPHeaderField:key];
+ }];
+ return urlRequest;
+}
+
+#pragma mark - Private
+
+- (NSDictionary *)headers {
+ return @{@"Accept-Version": kApiVersion}; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -144,13 +204,97 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+
+@protocol AnnouncementsFetcherDelegate;
+
+@interface AnnouncementsFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsFetcher.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+
+@interface AnnouncementsFetcher ()
+
+@property ( nonatomic, weak) id<AnnouncementsFetcherDelegate> delegate;
+
+@property ( nonatomic, strong) AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+- (void)fetchAnnouncements {
+
+ [self fetchAnnouncementsWithCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ NSArray *announcements = [self.builder announcementsFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementsFetched:announcements];
+ }
+
+ }];
+}
+
+- (void)fetchAnnouncementsWithCompletionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementsURLRequest] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementsURLRequest {
+ NSString *urlStr = [self announcementsURLString];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementsURLString {
+ return [NSString stringWithFormat:@"%@/%@", kServerUrl, kAnnouncementsResourcePath];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -159,7 +303,16 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@protocol AnnouncementsFetcherDelegate <NSObject>
+
+- (void)announcementsFetchingFailed;
+
+- (void)announcementsFetched:(NSArray *)announcements;
+
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -168,13 +321,139 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@class DetailViewController;
+
+@interface MasterViewController : UITableViewController
+
+@property (strong, nonatomic) DetailViewController *detailViewController;
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "MasterViewController.h"
+#import "DetailViewController.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementsFetcher.h"
+#import "AnnouncementsTableViewDataSource.h"
+#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+static NSString *kSegueShowDetail = @"showDetail";
+
+@interface MasterViewController () <AnnouncementsFetcherDelegate>
+
+@property NSMutableArray *objects;
+
+@property ( nonatomic, strong ) AnnouncementsFetcher *fetcher;
+
+@property ( nonatomic, strong) id<UITableViewDataSource> tableViewDataSource;
+@property ( nonatomic, strong) id<UITableViewDelegate> tableViewDelegate;
+
+@end
+
+@implementation MasterViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.tableViewDataSource = [[AnnouncementsTableViewDataSource alloc] init];
+ self.tableViewDelegate = [[AnnouncementsTableViewDelegate alloc] init];
+ self.tableView.dataSource = self.tableViewDataSource;
+ self.tableView.delegate = self.tableViewDelegate;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController.isCollapsed;
+ [super viewWillAppear:animated];
+
+ [self registerNotifications];
+ [self fetchAnnouncements]; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+ [self unregisterNotifications];
+}
+
+#pragma mark - Notifications
+
+-(void)registerNotifications {
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(announcementTapped:) name:kAnnouncementTappedNotification object:nil];
+}
+
+- (void)unregisterNotifications {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self name:kAnnouncementTappedNotification object:nil];
+}
+
+#pragma mark - Segues
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}
+
+#pragma mark - Private Methods
+
+- (void)announcementTapped:(NSNotification *)notification {
+ if([[notification object] isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)[notification object];
+ [self performSegueWithIdentifier:kSegueShowDetail sender:announcement];
+ }
+}
+
+- (void)setNetworkActivityIndicator:(BOOL)visible {
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible];
+}
+
+- (void)fetchAnnouncements {
+ [self setNetworkActivityIndicator:YES];
+ [self.fetcher fetchAnnouncements];
+}
+
+#pragma mark - AnnouncementsFetcherDelegate
+
+- (void)announcementsFetchingFailed {
+ [self setNetworkActivityIndicator:NO];
+}
+
+- (void)announcementsFetched:(NSArray *)announcements {
+ [self setNetworkActivityIndicator:NO];
+
+ if ( [self.tableViewDataSource isKindOfClass:[AnnouncementsTableViewDataSource class]]) {
+ ((AnnouncementsTableViewDataSource *)self.tableViewDataSource).announcements = announcements;
+ }
+ if ( [self.tableViewDelegate isKindOfClass:[AnnouncementsTableViewDelegate class]]) {
+ ((AnnouncementsTableViewDelegate *)self.tableViewDelegate).announcements = announcements;
+ }
+ [self.tableView reloadData]; <i class="conum" data-value="2"></i><b>(2)</b>
+}
+
+#pragma mark - Lazy
+
+- (AnnouncementsFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementsFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -195,25 +474,87 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@interface AnnouncementsTableViewDataSource : NSObject <UITableViewDataSource>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDataSource.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDataSource
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return [self.announcements count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ cell.textLabel.text = announcement.title;
+ }
+ }
+
+ return cell;
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+static NSString *kAnnouncementTappedNotification = @"AnnouncementTappedNotification";
+
+@interface AnnouncementsTableViewDelegate : NSObject <UITableViewDelegate>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDelegate
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAnnouncementTappedNotification <i class="conum" data-value="1"></i><b>(1)</b>
+ object:announcement
+ userInfo:nil];
+ }
+ }
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
index 84062e5937a..a586929708a 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
@@ -96,7 +96,22 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"2.0"; <i class="conum" data-value="1"></i><b>(1)</b>
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -116,7 +131,17 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcementPrimaryKey = announcement.primaryKey; <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -133,13 +158,99 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, assign) NSNumber *announcementPrimaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+#import "AnnouncementFetcher.h"
+
+@interface DetailViewController () <UIWebViewDelegate, AnnouncementFetcherDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@property ( nonatomic, strong ) AnnouncementFetcher *fetcher;
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+
+ if ( self.announcementPrimaryKey ) {
+ [self.fetcher fetchAnnouncement:self.announcementPrimaryKey];
+ }
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+#pragma mark - AnnouncementFetcherDelegate
+
+- (void)announcementFetchingFailed {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+- (void)announcementFetched:(Announcement *)announcement {
+ [[self activityIndicatorView] stopAnimating];
+ self.announcement = announcement;
+}
+
+
+#pragma mark - Lazy
+
+- (AnnouncementFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -148,13 +259,100 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+
+@interface AnnouncementFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+@interface AnnouncementFetcher ()
+
+@property ( nonatomic, weak )id<AnnouncementFetcherDelegate> delegate;
+@property ( nonatomic, strong )AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementFetcher
+
+#pragma mark - LifeCycle
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+#pragma mark - Public Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey {
+
+ [self fetchAnnouncement:primaryKey completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ Announcement *announcement = [self.builder announcementFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementFetched:announcement];
+ }
+ }];
+
+}
+
+#pragma mark - Private Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey completionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementURLRequest:primaryKey] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementURLRequest:(NSNumber *)primaryKey {
+ NSString *urlStr = [self announcementURLString:primaryKey];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementURLString:(NSNumber *)primaryKey {
+ return [NSString stringWithFormat:@"%@/%@/%@", kServerUrl, kAnnouncementsResourcePath, primaryKey];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -163,7 +361,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+@class Announcement;
+
+@protocol AnnouncementFetcherDelegate <NSObject>
+
+- (void)announcementFetchingFailed;
+
+- (void)announcementFetched:(Announcement *)announcement;
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/single.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/single.html
index 8293b428ea6..4dc15516aab 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/single.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/single.html
@@ -887,13 +887,25 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface Announcement : NSObject
+
+@property (nonatomic, copy)NSNumber *primaryKey;
+@property (nonatomic, copy)NSString *title;
+@property (nonatomic, copy)NSString *body;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "Announcement.h"
+
+@implementation Announcement
+
+@end</code></pre>
</div>
</div>
@@ -913,25 +925,255 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+#import "ElementBuilder.h"
+
+extern NSString *kAnnouncementBuilderErrorDomain;
+
+enum {
+ kAnnouncementBuilderInvalidJSONError,
+ kAnnouncementBuilderMissingDataError,
+};
+
+@interface AnnouncementBuilder : ElementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementBuilder.h"
+#import "Announcement.h"
+
+static NSString *kJSONKeyId = @"id";
+static NSString *kJSONKeyTitle = @"title";
+static NSString *kJSONKeyBody = @"body";
+
+@implementation AnnouncementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error {
+
+ return [super arrayFromJSON:objectNotation
+ key:nil
+ error:error
+ invalidJSONErrorCode:kAnnouncementBuilderInvalidJSONError
+ missingDataErrorCode:kAnnouncementBuilderMissingDataError
+ errorDomain:kAnnouncementBuilderErrorDomain];
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ Announcement *announcement = [[Announcement alloc] init];
+
+ if([[dict objectForKey:kJSONKeyId] isKindOfClass:[NSNumber class]]) {
+ announcement.primaryKey = (NSNumber *)[dict objectForKey:kJSONKeyId];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyTitle] isKindOfClass:[NSString class]]) {
+ announcement.title = (NSString *)[dict objectForKey:kJSONKeyTitle];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyBody] isKindOfClass:[NSString class]]) {
+ announcement.body = (NSString *)[dict objectForKey:kJSONKeyBody];
+ }
+
+ return announcement;
+
+}
+
+- ( NSError *)invalidJsonError {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ return [NSError errorWithDomain:kAnnouncementBuilderErrorDomain
+ code:kAnnouncementBuilderInvalidJSONError
+ userInfo:userInfo];
+}
+
+@end
+
+NSString *kAnnouncementBuilderErrorDomain = @"kAnnouncementBuilderErrorDomain";</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface ElementBuilder : NSObject
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain;
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "ElementBuilder.h"
+
+@implementation ElementBuilder
+
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ id parsedObject = [self parseJSON:objectNotation
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ errorDomain:errorDomain];
+
+ NSArray *elements = nil;
+ if ( [parsedObject isKindOfClass:[NSDictionary class]]) {
+ elements = [((NSDictionary *)parsedObject) objectForKey:key];
+
+ } else if ( [parsedObject isKindOfClass:[NSArray class]] ) {
+ elements = (NSArray *)parsedObject;
+ }
+
+ if (elements == nil) {
+ if (error != NULL) {
+ *error = [NSError errorWithDomain:errorDomain code:missingDataErrorCode userInfo:nil];
+ }
+ return nil;
+ }
+
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:[elements count]];
+ for (NSDictionary *parsedEl in elements) {
+ id el = [self newElementWithDictionary:parsedEl
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ missingDataErrorCode:missingDataErrorCode
+ errorDomain:errorDomain];
+ // Return nil becuase there has been an error in the previous method call
+ if(!el) {
+ return nil;
+ }
+
+ if([self isElementValid:el]) {
+ [results addObject:el];
+ }
+ }
+ return [results copy];
+}
+
+
+- (BOOL)isElementValid:(id)el {
+ // This may be overriden in a subclass
+ return YES;
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
+}
+
+
+- (id)parseJSON:(NSString *)objectNotation
+ error:(NSError **)error
+invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ NSParameterAssert(objectNotation != nil);
+ id jsonObject;
+
+ NSError *localError = nil;
+ if(objectNotation != nil) {
+ NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
+ jsonObject = [NSJSONSerialization JSONObjectWithData:unicodeNotation
+ options:0
+ error:&localError];
+ }
+ if (jsonObject == nil) {
+ if (error != NULL) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ if (localError != nil) {
+ [userInfo setObject:localError forKey:NSUnderlyingErrorKey];
+ }
+ *error = [NSError errorWithDomain:errorDomain code:invalidJSONErrorCode userInfo:userInfo];
+ }
+ return nil;
+ }
+ return jsonObject;
+}
+
++ (id)objectOrNull:(id)object {
+ if (!object || object == [NSNull null]) return nil;
+ return object;
+}
+
++ (BOOL)isNotNilAndNotArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isArrayTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isNumberTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+
+ return [self isNotNilTheKey:key atDict:dict] && ![self isStringTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSString class]]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSNumber class]]) {
+ return YES;
+ }
+ return NO;
+}
++ (BOOL)isArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSArray class]]) {
+ return YES;
+ }
+ return NO;
+}
+
+@end</code></pre>
</div>
</div>
@@ -970,7 +1212,22 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"1.0";
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1004,7 +1261,52 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "GrailsFetcher.h"
+
+@interface GrailsFetcher () <NSURLSessionDelegate>
+
+@end
+
+@implementation GrailsFetcher
+
+#pragma mark - LifeCycle
+
+- (id)init {
+ if(self = [super init]) {
+ NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ self.session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ }
+ return self;
+}
+
+
+#pragma mark - Public
+
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval {
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+ [urlRequest setHTTPMethod:@"GET"];
+
+ [[self headers] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
+ [urlRequest setValue:obj forHTTPHeaderField:key];
+ }];
+ return urlRequest;
+}
+
+#pragma mark - Private
+
+- (NSDictionary *)headers {
+ return @{@"Accept-Version": kApiVersion}; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1018,13 +1320,97 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+
+@protocol AnnouncementsFetcherDelegate;
+
+@interface AnnouncementsFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsFetcher.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+
+@interface AnnouncementsFetcher ()
+
+@property ( nonatomic, weak) id<AnnouncementsFetcherDelegate> delegate;
+
+@property ( nonatomic, strong) AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+- (void)fetchAnnouncements {
+
+ [self fetchAnnouncementsWithCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ NSArray *announcements = [self.builder announcementsFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementsFetched:announcements];
+ }
+
+ }];
+}
+
+- (void)fetchAnnouncementsWithCompletionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementsURLRequest] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementsURLRequest {
+ NSString *urlStr = [self announcementsURLString];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementsURLString {
+ return [NSString stringWithFormat:@"%@/%@", kServerUrl, kAnnouncementsResourcePath];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1033,7 +1419,16 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@protocol AnnouncementsFetcherDelegate <NSObject>
+
+- (void)announcementsFetchingFailed;
+
+- (void)announcementsFetched:(NSArray *)announcements;
+
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1042,13 +1437,139 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@class DetailViewController;
+
+@interface MasterViewController : UITableViewController
+
+@property (strong, nonatomic) DetailViewController *detailViewController;
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "MasterViewController.h"
+#import "DetailViewController.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementsFetcher.h"
+#import "AnnouncementsTableViewDataSource.h"
+#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+static NSString *kSegueShowDetail = @"showDetail";
+
+@interface MasterViewController () <AnnouncementsFetcherDelegate>
+
+@property NSMutableArray *objects;
+
+@property ( nonatomic, strong ) AnnouncementsFetcher *fetcher;
+
+@property ( nonatomic, strong) id<UITableViewDataSource> tableViewDataSource;
+@property ( nonatomic, strong) id<UITableViewDelegate> tableViewDelegate;
+
+@end
+
+@implementation MasterViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.tableViewDataSource = [[AnnouncementsTableViewDataSource alloc] init];
+ self.tableViewDelegate = [[AnnouncementsTableViewDelegate alloc] init];
+ self.tableView.dataSource = self.tableViewDataSource;
+ self.tableView.delegate = self.tableViewDelegate;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController.isCollapsed;
+ [super viewWillAppear:animated];
+
+ [self registerNotifications];
+ [self fetchAnnouncements]; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+ [self unregisterNotifications];
+}
+
+#pragma mark - Notifications
+
+-(void)registerNotifications {
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(announcementTapped:) name:kAnnouncementTappedNotification object:nil];
+}
+
+- (void)unregisterNotifications {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self name:kAnnouncementTappedNotification object:nil];
+}
+
+#pragma mark - Segues
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}
+
+#pragma mark - Private Methods
+
+- (void)announcementTapped:(NSNotification *)notification {
+ if([[notification object] isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)[notification object];
+ [self performSegueWithIdentifier:kSegueShowDetail sender:announcement];
+ }
+}
+
+- (void)setNetworkActivityIndicator:(BOOL)visible {
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible];
+}
+
+- (void)fetchAnnouncements {
+ [self setNetworkActivityIndicator:YES];
+ [self.fetcher fetchAnnouncements];
+}
+
+#pragma mark - AnnouncementsFetcherDelegate
+
+- (void)announcementsFetchingFailed {
+ [self setNetworkActivityIndicator:NO];
+}
+
+- (void)announcementsFetched:(NSArray *)announcements {
+ [self setNetworkActivityIndicator:NO];
+
+ if ( [self.tableViewDataSource isKindOfClass:[AnnouncementsTableViewDataSource class]]) {
+ ((AnnouncementsTableViewDataSource *)self.tableViewDataSource).announcements = announcements;
+ }
+ if ( [self.tableViewDelegate isKindOfClass:[AnnouncementsTableViewDelegate class]]) {
+ ((AnnouncementsTableViewDelegate *)self.tableViewDelegate).announcements = announcements;
+ }
+ [self.tableView reloadData]; <i class="conum" data-value="2"></i><b>(2)</b>
+}
+
+#pragma mark - Lazy
+
+- (AnnouncementsFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementsFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1069,25 +1590,87 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@interface AnnouncementsTableViewDataSource : NSObject <UITableViewDataSource>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDataSource.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDataSource
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return [self.announcements count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ cell.textLabel.text = announcement.title;
+ }
+ }
+
+ return cell;
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+static NSString *kAnnouncementTappedNotification = @"AnnouncementTappedNotification";
+
+@interface AnnouncementsTableViewDelegate : NSObject <UITableViewDelegate>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDelegate
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAnnouncementTappedNotification <i class="conum" data-value="1"></i><b>(1)</b>
+ object:announcement
+ userInfo:nil];
+ }
+ }
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1118,7 +1701,17 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="imageblock">
@@ -1140,13 +1733,68 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+
+@interface DetailViewController () <UIWebViewDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+@end</code></pre>
</div>
</div>
@@ -1394,7 +2042,22 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"2.0"; <i class="conum" data-value="1"></i><b>(1)</b>
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1414,7 +2077,17 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcementPrimaryKey = announcement.primaryKey; <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1431,13 +2104,99 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, assign) NSNumber *announcementPrimaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+#import "AnnouncementFetcher.h"
+
+@interface DetailViewController () <UIWebViewDelegate, AnnouncementFetcherDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@property ( nonatomic, strong ) AnnouncementFetcher *fetcher;
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+
+ if ( self.announcementPrimaryKey ) {
+ [self.fetcher fetchAnnouncement:self.announcementPrimaryKey];
+ }
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+#pragma mark - AnnouncementFetcherDelegate
+
+- (void)announcementFetchingFailed {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+- (void)announcementFetched:(Announcement *)announcement {
+ [[self activityIndicatorView] stopAnimating];
+ self.announcement = announcement;
+}
+
+
+#pragma mark - Lazy
+
+- (AnnouncementFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1446,13 +2205,100 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+
+@interface AnnouncementFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+@interface AnnouncementFetcher ()
+
+@property ( nonatomic, weak )id<AnnouncementFetcherDelegate> delegate;
+@property ( nonatomic, strong )AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementFetcher
+
+#pragma mark - LifeCycle
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+#pragma mark - Public Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey {
+
+ [self fetchAnnouncement:primaryKey completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ Announcement *announcement = [self.builder announcementFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementFetched:announcement];
+ }
+ }];
+
+}
+
+#pragma mark - Private Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey completionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementURLRequest:primaryKey] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementURLRequest:(NSNumber *)primaryKey {
+ NSString *urlStr = [self announcementURLString:primaryKey];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementURLString:(NSNumber *)primaryKey {
+ return [NSString stringWithFormat:@"%@/%@/%@", kServerUrl, kAnnouncementsResourcePath, primaryKey];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1461,7 +2307,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+@class Announcement;
+
+@protocol AnnouncementFetcherDelegate <NSObject>
+
+- (void)announcementFetchingFailed;
+
+- (void)announcementFetched:(Announcement *)announcement;
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/version2.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/version2.html
index 33cbd5631f8..a4b08196b3c 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/version2.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/version2.html
@@ -338,7 +338,22 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"2.0"; <i class="conum" data-value="1"></i><b>(1)</b>
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -358,7 +373,17 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcementPrimaryKey = announcement.primaryKey; <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -375,13 +400,99 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, assign) NSNumber *announcementPrimaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+#import "AnnouncementFetcher.h"
+
+@interface DetailViewController () <UIWebViewDelegate, AnnouncementFetcherDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@property ( nonatomic, strong ) AnnouncementFetcher *fetcher;
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+
+ if ( self.announcementPrimaryKey ) {
+ [self.fetcher fetchAnnouncement:self.announcementPrimaryKey];
+ }
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+#pragma mark - AnnouncementFetcherDelegate
+
+- (void)announcementFetchingFailed {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+- (void)announcementFetched:(Announcement *)announcement {
+ [[self activityIndicatorView] stopAnimating];
+ self.announcement = announcement;
+}
+
+
+#pragma mark - Lazy
+
+- (AnnouncementFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -390,13 +501,100 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+
+@interface AnnouncementFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementFetcher.h"
+#import "AnnouncementFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+@interface AnnouncementFetcher ()
+
+@property ( nonatomic, weak )id<AnnouncementFetcherDelegate> delegate;
+@property ( nonatomic, strong )AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementFetcher
+
+#pragma mark - LifeCycle
+
+- (id)initWithDelegate:(id<AnnouncementFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+#pragma mark - Public Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey {
+
+ [self fetchAnnouncement:primaryKey completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ Announcement *announcement = [self.builder announcementFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementFetched:announcement];
+ }
+ }];
+
+}
+
+#pragma mark - Private Methods
+
+- (void)fetchAnnouncement:(NSNumber *)primaryKey completionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementURLRequest:primaryKey] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementURLRequest:(NSNumber *)primaryKey {
+ NSString *urlStr = [self announcementURLString:primaryKey];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementURLString:(NSNumber *)primaryKey {
+ return [NSString stringWithFormat:@"%@/%@/%@", kServerUrl, kAnnouncementsResourcePath, primaryKey];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -405,7 +603,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v2/IntranetClient/AnnouncementFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+@class Announcement;
+
+@protocol AnnouncementFetcherDelegate <NSObject>
+
+- (void)announcementFetchingFailed;
+
+- (void)announcementFetched:(Announcement *)announcement;
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
index 6ce2da92864..6b8d4491d97 100644
--- a/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
+++ b/guides/building-an-ios-objectc-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
@@ -151,13 +151,25 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface Announcement : NSObject
+
+@property (nonatomic, copy)NSNumber *primaryKey;
+@property (nonatomic, copy)NSString *title;
+@property (nonatomic, copy)NSString *body;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/Announcement.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/Announcement.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "Announcement.h"
+
+@implementation Announcement
+
+@end</code></pre>
</div>
</div>
@@ -177,25 +189,255 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+#import "ElementBuilder.h"
+
+extern NSString *kAnnouncementBuilderErrorDomain;
+
+enum {
+ kAnnouncementBuilderInvalidJSONError,
+ kAnnouncementBuilderMissingDataError,
+};
+
+@interface AnnouncementBuilder : ElementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementBuilder.h"
+#import "Announcement.h"
+
+static NSString *kJSONKeyId = @"id";
+static NSString *kJSONKeyTitle = @"title";
+static NSString *kJSONKeyBody = @"body";
+
+@implementation AnnouncementBuilder
+
+- (NSArray *)announcementsFromJSON:(NSString *)objectNotation
+ error:(NSError **)error {
+
+ return [super arrayFromJSON:objectNotation
+ key:nil
+ error:error
+ invalidJSONErrorCode:kAnnouncementBuilderInvalidJSONError
+ missingDataErrorCode:kAnnouncementBuilderMissingDataError
+ errorDomain:kAnnouncementBuilderErrorDomain];
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ Announcement *announcement = [[Announcement alloc] init];
+
+ if([[dict objectForKey:kJSONKeyId] isKindOfClass:[NSNumber class]]) {
+ announcement.primaryKey = (NSNumber *)[dict objectForKey:kJSONKeyId];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyTitle] isKindOfClass:[NSString class]]) {
+ announcement.title = (NSString *)[dict objectForKey:kJSONKeyTitle];
+ } else {
+ if(error != NULL) {
+ *error = [self invalidJsonError];
+ }
+ return nil;
+ }
+
+ if([[dict objectForKey:kJSONKeyBody] isKindOfClass:[NSString class]]) {
+ announcement.body = (NSString *)[dict objectForKey:kJSONKeyBody];
+ }
+
+ return announcement;
+
+}
+
+- ( NSError *)invalidJsonError {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ return [NSError errorWithDomain:kAnnouncementBuilderErrorDomain
+ code:kAnnouncementBuilderInvalidJSONError
+ userInfo:userInfo];
+}
+
+@end
+
+NSString *kAnnouncementBuilderErrorDomain = @"kAnnouncementBuilderErrorDomain";</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@interface ElementBuilder : NSObject
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain;
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/ElementBuilder.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "ElementBuilder.h"
+
+@implementation ElementBuilder
+
+
+- (NSArray *)arrayFromJSON:(NSString *)objectNotation
+ key:(NSString *)key
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ id parsedObject = [self parseJSON:objectNotation
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ errorDomain:errorDomain];
+
+ NSArray *elements = nil;
+ if ( [parsedObject isKindOfClass:[NSDictionary class]]) {
+ elements = [((NSDictionary *)parsedObject) objectForKey:key];
+
+ } else if ( [parsedObject isKindOfClass:[NSArray class]] ) {
+ elements = (NSArray *)parsedObject;
+ }
+
+ if (elements == nil) {
+ if (error != NULL) {
+ *error = [NSError errorWithDomain:errorDomain code:missingDataErrorCode userInfo:nil];
+ }
+ return nil;
+ }
+
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:[elements count]];
+ for (NSDictionary *parsedEl in elements) {
+ id el = [self newElementWithDictionary:parsedEl
+ error:error
+ invalidJSONErrorCode:invalidJSONErrorCode
+ missingDataErrorCode:missingDataErrorCode
+ errorDomain:errorDomain];
+ // Return nil becuase there has been an error in the previous method call
+ if(!el) {
+ return nil;
+ }
+
+ if([self isElementValid:el]) {
+ [results addObject:el];
+ }
+ }
+ return [results copy];
+}
+
+
+- (BOOL)isElementValid:(id)el {
+ // This may be overriden in a subclass
+ return YES;
+}
+
+- (id)newElementWithDictionary:(NSDictionary *)dict
+ error:(NSError **)error
+ invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ missingDataErrorCode:(NSInteger)missingDataErrorCode
+ errorDomain:(NSString *)errorDomain {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
+}
+
+
+- (id)parseJSON:(NSString *)objectNotation
+ error:(NSError **)error
+invalidJSONErrorCode:(NSInteger)invalidJSONErrorCode
+ errorDomain:(NSString *)errorDomain {
+
+ NSParameterAssert(objectNotation != nil);
+ id jsonObject;
+
+ NSError *localError = nil;
+ if(objectNotation != nil) {
+ NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
+ jsonObject = [NSJSONSerialization JSONObjectWithData:unicodeNotation
+ options:0
+ error:&localError];
+ }
+ if (jsonObject == nil) {
+ if (error != NULL) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
+ if (localError != nil) {
+ [userInfo setObject:localError forKey:NSUnderlyingErrorKey];
+ }
+ *error = [NSError errorWithDomain:errorDomain code:invalidJSONErrorCode userInfo:userInfo];
+ }
+ return nil;
+ }
+ return jsonObject;
+}
+
++ (id)objectOrNull:(id)object {
+ if (!object || object == [NSNull null]) return nil;
+ return object;
+}
+
++ (BOOL)isNotNilAndNotArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isArrayTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ return [self isNotNilTheKey:key atDict:dict] && ![self isNumberTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilAndNotStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+
+ return [self isNotNilTheKey:key atDict:dict] && ![self isStringTheKey:key atDict:dict];
+}
+
++ (BOOL)isNotNilTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isStringTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSString class]]) {
+ return YES;
+ }
+ return NO;
+}
+
++ (BOOL)isNumberTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSNumber class]]) {
+ return YES;
+ }
+ return NO;
+}
++ (BOOL)isArrayTheKey:(NSString *)key atDict:(NSDictionary *)dict {
+ if(dict[key] != (id)[NSNull null] && dict[key] && [dict[key] isKindOfClass:[NSArray class]]) {
+ return YES;
+ }
+ return NO;
+}
+
+@end</code></pre>
</div>
</div>
@@ -234,7 +476,22 @@ announcements exposed by the Grails application</p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+static NSString *kServerUrl = @"http://192.168.1.40:8080";
+static NSString *kApiVersion = @"1.0";
+static NSString *kAnnouncementsResourcePath = @"announcements";
+static NSInteger FAST_TIME_INTERVAL = 5.0;
+
+@interface GrailsFetcher : NSObject
+
+@property (nonatomic, strong) NSURLSession *session;
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval;
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -268,7 +525,52 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/GrailsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "GrailsFetcher.h"
+
+@interface GrailsFetcher () <NSURLSessionDelegate>
+
+@end
+
+@implementation GrailsFetcher
+
+#pragma mark - LifeCycle
+
+- (id)init {
+ if(self = [super init]) {
+ NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ self.session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ }
+ return self;
+}
+
+
+#pragma mark - Public
+
+
+- (NSURLRequest *)getURLRequestWithUrlString:(NSString *)urlString
+ cachePolicy:(NSURLRequestCachePolicy)cachePolicy
+ timeoutInterval:(NSTimeInterval)timeoutInterval {
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+ [urlRequest setHTTPMethod:@"GET"];
+
+ [[self headers] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
+ [urlRequest setValue:obj forHTTPHeaderField:key];
+ }];
+ return urlRequest;
+}
+
+#pragma mark - Private
+
+- (NSDictionary *)headers {
+ return @{@"Accept-Version": kApiVersion}; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -282,13 +584,97 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+#import "GrailsFetcher.h"
+
+@protocol AnnouncementsFetcherDelegate;
+
+@interface AnnouncementsFetcher : GrailsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate;
+
+- (void)fetchAnnouncements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcher.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsFetcher.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementBuilder.h"
+
+
+@interface AnnouncementsFetcher ()
+
+@property ( nonatomic, weak) id<AnnouncementsFetcherDelegate> delegate;
+
+@property ( nonatomic, strong) AnnouncementBuilder *builder;
+
+@end
+
+@implementation AnnouncementsFetcher
+
+- (id)initWithDelegate:(id<AnnouncementsFetcherDelegate>)delegate {
+ if(self = [super init]) {
+ self.delegate = delegate;
+ self.builder = [[AnnouncementBuilder alloc] init];
+ }
+ return self;
+}
+
+- (void)fetchAnnouncements {
+
+ [self fetchAnnouncementsWithCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if(error) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSInteger statusCode = [((NSHTTPURLResponse*)response) statusCode];
+ if(statusCode != 200) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+
+ NSString *objectNotation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSError *builderError;
+ NSArray *announcements = [self.builder announcementsFromJSON:objectNotation error:&builderError];
+ if(builderError) {
+ if ( self.delegate ) {
+ [self.delegate announcementsFetchingFailed];
+ }
+ return;
+ }
+ if ( self.delegate ) {
+ [self.delegate announcementsFetched:announcements];
+ }
+
+ }];
+}
+
+- (void)fetchAnnouncementsWithCompletionHandler:(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self announcementsURLRequest] completionHandler:completionHandler];
+ [dataTask resume];
+}
+
+- (NSURLRequest *)announcementsURLRequest {
+ NSString *urlStr = [self announcementsURLString];
+ return [super getURLRequestWithUrlString:urlStr
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:FAST_TIME_INTERVAL];
+}
+
+- (NSString *)announcementsURLString {
+ return [NSString stringWithFormat:@"%@/%@", kServerUrl, kAnnouncementsResourcePath];
+}
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -297,7 +683,16 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <Foundation/Foundation.h>
+
+@protocol AnnouncementsFetcherDelegate <NSObject>
+
+- (void)announcementsFetchingFailed;
+
+- (void)announcementsFetched:(NSArray *)announcements;
+
+
+@end</code></pre>
</div>
</div>
<div class="paragraph">
@@ -306,13 +701,139 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@class DetailViewController;
+
+@interface MasterViewController : UITableViewController
+
+@property (strong, nonatomic) DetailViewController *detailViewController;
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "MasterViewController.h"
+#import "DetailViewController.h"
+#import "AnnouncementsFetcherDelegate.h"
+#import "AnnouncementsFetcher.h"
+#import "AnnouncementsTableViewDataSource.h"
+#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+static NSString *kSegueShowDetail = @"showDetail";
+
+@interface MasterViewController () <AnnouncementsFetcherDelegate>
+
+@property NSMutableArray *objects;
+
+@property ( nonatomic, strong ) AnnouncementsFetcher *fetcher;
+
+@property ( nonatomic, strong) id<UITableViewDataSource> tableViewDataSource;
+@property ( nonatomic, strong) id<UITableViewDelegate> tableViewDelegate;
+
+@end
+
+@implementation MasterViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.tableViewDataSource = [[AnnouncementsTableViewDataSource alloc] init];
+ self.tableViewDelegate = [[AnnouncementsTableViewDelegate alloc] init];
+ self.tableView.dataSource = self.tableViewDataSource;
+ self.tableView.delegate = self.tableViewDelegate;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController.isCollapsed;
+ [super viewWillAppear:animated];
+
+ [self registerNotifications];
+ [self fetchAnnouncements]; <i class="conum" data-value="1"></i><b>(1)</b>
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+ [self unregisterNotifications];
+}
+
+#pragma mark - Notifications
+
+-(void)registerNotifications {
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(announcementTapped:) name:kAnnouncementTappedNotification object:nil];
+}
+
+- (void)unregisterNotifications {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self name:kAnnouncementTappedNotification object:nil];
+}
+
+#pragma mark - Segues
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}
+
+#pragma mark - Private Methods
+
+- (void)announcementTapped:(NSNotification *)notification {
+ if([[notification object] isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)[notification object];
+ [self performSegueWithIdentifier:kSegueShowDetail sender:announcement];
+ }
+}
+
+- (void)setNetworkActivityIndicator:(BOOL)visible {
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible];
+}
+
+- (void)fetchAnnouncements {
+ [self setNetworkActivityIndicator:YES];
+ [self.fetcher fetchAnnouncements];
+}
+
+#pragma mark - AnnouncementsFetcherDelegate
+
+- (void)announcementsFetchingFailed {
+ [self setNetworkActivityIndicator:NO];
+}
+
+- (void)announcementsFetched:(NSArray *)announcements {
+ [self setNetworkActivityIndicator:NO];
+
+ if ( [self.tableViewDataSource isKindOfClass:[AnnouncementsTableViewDataSource class]]) {
+ ((AnnouncementsTableViewDataSource *)self.tableViewDataSource).announcements = announcements;
+ }
+ if ( [self.tableViewDelegate isKindOfClass:[AnnouncementsTableViewDelegate class]]) {
+ ((AnnouncementsTableViewDelegate *)self.tableViewDelegate).announcements = announcements;
+ }
+ [self.tableView reloadData]; <i class="conum" data-value="2"></i><b>(2)</b>
+}
+
+#pragma mark - Lazy
+
+- (AnnouncementsFetcher *)fetcher {
+ if(!_fetcher) {
+ _fetcher = [[AnnouncementsFetcher alloc] initWithDelegate:self];
+ }
+ return _fetcher;
+}
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -333,25 +854,87 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+@interface AnnouncementsTableViewDataSource : NSObject <UITableViewDataSource>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDataSource.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDataSource
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return [self.announcements count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ cell.textLabel.text = announcement.title;
+ }
+ }
+
+ return cell;
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+
+static NSString *kAnnouncementTappedNotification = @"AnnouncementTappedNotification";
+
+@interface AnnouncementsTableViewDelegate : NSObject <UITableViewDelegate>
+
+@property (nonatomic, strong) NSArray *announcements;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "AnnouncementsTableViewDelegate.h"
+#import "Announcement.h"
+
+@implementation AnnouncementsTableViewDelegate
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if ( [self.announcements count] > indexPath.row ) {
+ id obj = self.announcements[indexPath.row];
+ if ( [obj isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)obj;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAnnouncementTappedNotification <i class="conum" data-value="1"></i><b>(1)</b>
+ object:announcement
+ userInfo:nil];
+ }
+ }
+}
+
+
+@end</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -382,7 +965,17 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/MasterViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/MasterViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+ if ([[segue identifier] isEqualToString:kSegueShowDetail]) {
+ DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
+ if([sender isKindOfClass:[Announcement class]]) {
+ Announcement *announcement = (Announcement *)sender;
+ controller.announcement = announcement;
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
+ controller.navigationItem.leftItemsSupplementBackButton = YES;
+ }
+}</code></pre>
</div>
</div>
<div class="imageblock">
@@ -404,13 +997,68 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.h[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import <UIKit/UIKit.h>
+#import "Announcement.h"
+
+@interface DetailViewController : UIViewController
+
+@property (nonatomic, strong) Announcement *announcement;
+
+@end</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-objectivec-ios-v1/IntranetClient/DetailViewController.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="objectivec">link:{sourcedir}/../complete-objectivec-ios-v1/IntranetClient/DetailViewController.m[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="objectivec">#import "DetailViewController.h"
+#import "Announcement.h"
+
+@interface DetailViewController () <UIWebViewDelegate>
+
+@property ( nonatomic, weak) IBOutlet UILabel *titleLabel;
+@property ( nonatomic, weak) IBOutlet UIWebView *webView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
+
+@end
+
+@implementation DetailViewController
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ if (self.announcement) {
+ self.titleLabel.text = self.announcement.title;
+ [[self activityIndicatorView] startAnimating];
+ [self.webView loadHTMLString:self.announcement.body baseURL:nil];
+ }
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.webView.delegate = self;
+ // Do any additional setup after loading the view, typically from a nib.
+ [self configureView];
+}
+
+#pragma mark - Managing the detail item
+
+- (void)setAnnouncement:(Announcement *)announcement {
+ if (_announcement != announcement) {
+ _announcement = announcement;
+
+ // Update the view.
+ [self configureView];
+ }
+}
+
+#pragma mark - UIWebViewDelegate
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [[self activityIndicatorView] stopAnimating];
+}
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [[self activityIndicatorView] stopAnimating];
+}
+
+@end</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/index.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/index.html
index 07d2cb24617..352d709ec33 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/index.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/index.html
@@ -886,7 +886,16 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/Announcement.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">class Announcement {
+ let primaryKey: Int
+ let title: String
+ var body: String?
+
+ init(primaryKey: Int, title: String) {
+ self.primaryKey = primaryKey
+ self.title = title
+ }
+}</code></pre>
</div>
</div>
@@ -906,7 +915,29 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementBuilder {
+
+ func announcementsFromJSON(_ data: Data?) -> [Announcement] {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ var announcements = [Announcement]()
+ if let array = json as? [Any] {
+ for object in array {
+ if let dict = object as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ announcements.append(announcement)
+ }
+ }
+ }
+ }
+ return announcements
+ }
+}</code></pre>
</div>
</div>
@@ -926,7 +957,13 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/GrailsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements" <i class="conum" data-value="2"></i><b>(2)</b>
+ }
+ static let version = "1.0" <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -960,7 +997,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementsFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate:AnnouncementsFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncements() {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementsFetchingFailed()
+ }
+ return
+ }
+ let announcements = self.builder.announcementsFromJSON(data)
+ if let delegate = self.delegate {
+ delegate.announcementsFetched(announcements)
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -977,7 +1044,12 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementsFetcherDelegate: class {
+
+ func announcementsFetchingFailed()
+
+ func announcementsFetched(_ announcements: [Announcement])
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -986,7 +1058,99 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class MasterViewController: UITableViewController, AnnouncementsFetcherDelegate {
+
+ var detailViewController: DetailViewController? = nil
+ var objects = [Any]()
+
+ var tableViewDataSource = AnnouncementsTableViewDataSource()
+ var tableViewDelegate = AnnouncementsTableViewDelegate()
+ let fetcher = AnnouncementsFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.tableView.dataSource = self.tableViewDataSource
+ self.tableView.delegate = self.tableViewDelegate
+ self.fetcher.delegate = self
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
+ super.viewWillAppear(animated)
+ self.registerNotifications()
+ self.fetchAnnouncements() <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ self.unregisterNotifications()
+ }
+
+ // MARK: - Notifications
+
+ func registerNotifications() {
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.announcementTapped),
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ func unregisterNotifications() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ // MARK: - Private Methods
+
+ @objc func announcementTapped(notification: NSNotification){
+
+ let announcement = notification.object as! Announcement
+ self.performSegue(withIdentifier: "showDetail", sender: announcement)
+ }
+
+ func fetchAnnouncements() {
+ self.setNetworkActivityIndicator(true)
+ self.fetcher.fetchAnnouncements()
+ }
+
+ func setNetworkActivityIndicator(_ visible: Bool) {
+ UIApplication.shared.isNetworkActivityIndicatorVisible = visible
+ }
+
+ // MARK: - Segues
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }
+
+ // MARK: - AnnouncementsFetcherDelegate
+
+ func announcementsFetchingFailed() {
+ self.setNetworkActivityIndicator(false)
+ }
+
+ func announcementsFetched(_ announcements: [Announcement]) { <i class="conum" data-value="2"></i><b>(2)</b>
+ self.setNetworkActivityIndicator(false)
+
+ self.tableViewDataSource.announcements = announcements
+ self.tableViewDelegate.announcements = announcements
+ self.tableView.reloadData()
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1007,13 +1171,43 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDataSource : NSObject, UITableViewDataSource {
+
+ var announcements = [Announcement]()
+
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return announcements.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+
+ let announcement = announcements[indexPath.row]
+ cell.textLabel!.text = announcement.title
+ return cell
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDelegate : NSObject, UITableViewDelegate {
+
+ var announcements = [Announcement]()
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let announcement = announcements[indexPath.row]
+ NotificationCenter.default.post(name: Notification.Name("AnnouncementTappedNotification"), object: announcement) <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1044,7 +1238,15 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }</code></pre>
</div>
</div>
<div class="imageblock">
@@ -1066,7 +1268,61 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+ self.configureView()
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ // Update the view.
+ self.configureView()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+}</code></pre>
</div>
</div>
@@ -1314,7 +1570,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/GrailsApi.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080"
+ static let version = "2.0" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements"
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1334,7 +1596,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcementPrimaryKey = announcement.primaryKey
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1351,7 +1622,86 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate, AnnouncementFetcherDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ let fetcher = AnnouncementFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.fetcher.delegate = self
+ self.configureView()
+ }
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ self.configureView()
+ }
+ }
+
+ var announcementPrimaryKey: Int? {
+ didSet {
+ self.showActivityIndicator()
+ self.fetcher.fetchAnnouncement(self.announcementPrimaryKey!)
+ }
+ }
+
+ // MARK: - Private Methods
+ func showActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.startAnimating()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+
+ // Mark: - AnnouncementFetcherDelegate
+
+ func announcementFetchingFailed() {
+
+ }
+
+ func announcementFetched(_ announcement: Announcement) {
+ self.announcement = announcement
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1360,7 +1710,42 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate: AnnouncementFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncement(_ primaryKey: Int) {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)/\(primaryKey)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ return
+ }
+ if let announcement = self.builder.announcementFromJSON(data) {
+ if let delegate = self.delegate {
+ delegate.announcementFetched(announcement)
+ }
+ } else {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1369,7 +1754,20 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> func announcementFromJSON(_ data: Data?) -> Announcement? {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ if let dict = json as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ return announcement
+
+ }
+ }
+ return nil
+ }</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1378,7 +1776,12 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementFetcherDelegate: class {
+
+ func announcementFetchingFailed()
+
+ func announcementFetched(_ announcement: Announcement)
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
index 39317c2136a..19972d87f99 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/detailViewController.html
@@ -98,7 +98,15 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }</code></pre>
</div>
</div>
<div class="imageblock">
@@ -120,7 +128,61 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+ self.configureView()
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ // Update the view.
+ self.configureView()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
index f4ffbf4ac05..44ea91e1255 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosJsonToModel.html
@@ -96,7 +96,29 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-swift-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementBuilder {
+
+ func announcementsFromJSON(_ data: Data?) -> [Announcement] {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ var announcements = [Announcement]()
+ if let array = json as? [Any] {
+ for object in array {
+ if let dict = object as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ announcements.append(announcement)
+ }
+ }
+ }
+ }
+ return announcements
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
index ee242606066..a09c7824e1f 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/iosModel.html
@@ -96,7 +96,16 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/Announcement.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">class Announcement {
+ let primaryKey: Int
+ let title: String
+ var body: String?
+
+ init(primaryKey: Int, title: String) {
+ self.primaryKey = primaryKey
+ self.title = title
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
index 7bdc5ee24f9..f166761bde3 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/networkingCode.html
@@ -96,7 +96,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/GrailsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements" <i class="conum" data-value="2"></i><b>(2)</b>
+ }
+ static let version = "1.0" <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -130,7 +136,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementsFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate:AnnouncementsFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncements() {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementsFetchingFailed()
+ }
+ return
+ }
+ let announcements = self.builder.announcementsFromJSON(data)
+ if let delegate = self.delegate {
+ delegate.announcementsFetched(announcements)
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -147,7 +183,12 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementsFetcherDelegate: class {
+
+ func announcementsFetchingFailed()
+
+ func announcementsFetched(_ announcements: [Announcement])
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -156,7 +197,99 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class MasterViewController: UITableViewController, AnnouncementsFetcherDelegate {
+
+ var detailViewController: DetailViewController? = nil
+ var objects = [Any]()
+
+ var tableViewDataSource = AnnouncementsTableViewDataSource()
+ var tableViewDelegate = AnnouncementsTableViewDelegate()
+ let fetcher = AnnouncementsFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.tableView.dataSource = self.tableViewDataSource
+ self.tableView.delegate = self.tableViewDelegate
+ self.fetcher.delegate = self
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
+ super.viewWillAppear(animated)
+ self.registerNotifications()
+ self.fetchAnnouncements() <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ self.unregisterNotifications()
+ }
+
+ // MARK: - Notifications
+
+ func registerNotifications() {
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.announcementTapped),
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ func unregisterNotifications() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ // MARK: - Private Methods
+
+ @objc func announcementTapped(notification: NSNotification){
+
+ let announcement = notification.object as! Announcement
+ self.performSegue(withIdentifier: "showDetail", sender: announcement)
+ }
+
+ func fetchAnnouncements() {
+ self.setNetworkActivityIndicator(true)
+ self.fetcher.fetchAnnouncements()
+ }
+
+ func setNetworkActivityIndicator(_ visible: Bool) {
+ UIApplication.shared.isNetworkActivityIndicatorVisible = visible
+ }
+
+ // MARK: - Segues
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }
+
+ // MARK: - AnnouncementsFetcherDelegate
+
+ func announcementsFetchingFailed() {
+ self.setNetworkActivityIndicator(false)
+ }
+
+ func announcementsFetched(_ announcements: [Announcement]) { <i class="conum" data-value="2"></i><b>(2)</b>
+ self.setNetworkActivityIndicator(false)
+
+ self.tableViewDataSource.announcements = announcements
+ self.tableViewDelegate.announcements = announcements
+ self.tableView.reloadData()
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -177,13 +310,43 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDataSource : NSObject, UITableViewDataSource {
+
+ var announcements = [Announcement]()
+
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return announcements.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+
+ let announcement = announcements[indexPath.row]
+ cell.textLabel!.text = announcement.title
+ return cell
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDelegate : NSObject, UITableViewDelegate {
+
+ var announcements = [Announcement]()
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let announcement = announcements[indexPath.row]
+ NotificationCenter.default.post(name: Notification.Name("AnnouncementTappedNotification"), object: announcement) <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+}</code></pre>
</div>
</div>
<div class="colist arabic">
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
index 7f2ad35060b..f7def8f097e 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/pages/v2iOS.html
@@ -96,7 +96,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/GrailsApi.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080"
+ static let version = "2.0" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements"
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -116,7 +122,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcementPrimaryKey = announcement.primaryKey
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -133,7 +148,86 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate, AnnouncementFetcherDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ let fetcher = AnnouncementFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.fetcher.delegate = self
+ self.configureView()
+ }
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ self.configureView()
+ }
+ }
+
+ var announcementPrimaryKey: Int? {
+ didSet {
+ self.showActivityIndicator()
+ self.fetcher.fetchAnnouncement(self.announcementPrimaryKey!)
+ }
+ }
+
+ // MARK: - Private Methods
+ func showActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.startAnimating()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+
+ // Mark: - AnnouncementFetcherDelegate
+
+ func announcementFetchingFailed() {
+
+ }
+
+ func announcementFetched(_ announcement: Announcement) {
+ self.announcement = announcement
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -142,7 +236,42 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate: AnnouncementFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncement(_ primaryKey: Int) {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)/\(primaryKey)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ return
+ }
+ if let announcement = self.builder.announcementFromJSON(data) {
+ if let delegate = self.delegate {
+ delegate.announcementFetched(announcement)
+ }
+ } else {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -151,7 +280,20 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> func announcementFromJSON(_ data: Data?) -> Announcement? {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ if let dict = json as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ return announcement
+
+ }
+ }
+ return nil
+ }</code></pre>
</div>
</div>
<div class="paragraph">
@@ -160,7 +302,12 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementFetcherDelegate: class {
+
+ func announcementFetchingFailed()
+
+ func announcementFetched(_ announcement: Announcement)
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/single.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/single.html
index 07d2cb24617..352d709ec33 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/single.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/single.html
@@ -886,7 +886,16 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/Announcement.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">class Announcement {
+ let primaryKey: Int
+ let title: String
+ var body: String?
+
+ init(primaryKey: Int, title: String) {
+ self.primaryKey = primaryKey
+ self.title = title
+ }
+}</code></pre>
</div>
</div>
@@ -906,7 +915,29 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementBuilder {
+
+ func announcementsFromJSON(_ data: Data?) -> [Announcement] {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ var announcements = [Announcement]()
+ if let array = json as? [Any] {
+ for object in array {
+ if let dict = object as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ announcements.append(announcement)
+ }
+ }
+ }
+ }
+ return announcements
+ }
+}</code></pre>
</div>
</div>
@@ -926,7 +957,13 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/GrailsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements" <i class="conum" data-value="2"></i><b>(2)</b>
+ }
+ static let version = "1.0" <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -960,7 +997,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementsFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate:AnnouncementsFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncements() {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementsFetchingFailed()
+ }
+ return
+ }
+ let announcements = self.builder.announcementsFromJSON(data)
+ if let delegate = self.delegate {
+ delegate.announcementsFetched(announcements)
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -977,7 +1044,12 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementsFetcherDelegate: class {
+
+ func announcementsFetchingFailed()
+
+ func announcementsFetched(_ announcements: [Announcement])
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -986,7 +1058,99 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class MasterViewController: UITableViewController, AnnouncementsFetcherDelegate {
+
+ var detailViewController: DetailViewController? = nil
+ var objects = [Any]()
+
+ var tableViewDataSource = AnnouncementsTableViewDataSource()
+ var tableViewDelegate = AnnouncementsTableViewDelegate()
+ let fetcher = AnnouncementsFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.tableView.dataSource = self.tableViewDataSource
+ self.tableView.delegate = self.tableViewDelegate
+ self.fetcher.delegate = self
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
+ super.viewWillAppear(animated)
+ self.registerNotifications()
+ self.fetchAnnouncements() <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ self.unregisterNotifications()
+ }
+
+ // MARK: - Notifications
+
+ func registerNotifications() {
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.announcementTapped),
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ func unregisterNotifications() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ // MARK: - Private Methods
+
+ @objc func announcementTapped(notification: NSNotification){
+
+ let announcement = notification.object as! Announcement
+ self.performSegue(withIdentifier: "showDetail", sender: announcement)
+ }
+
+ func fetchAnnouncements() {
+ self.setNetworkActivityIndicator(true)
+ self.fetcher.fetchAnnouncements()
+ }
+
+ func setNetworkActivityIndicator(_ visible: Bool) {
+ UIApplication.shared.isNetworkActivityIndicatorVisible = visible
+ }
+
+ // MARK: - Segues
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }
+
+ // MARK: - AnnouncementsFetcherDelegate
+
+ func announcementsFetchingFailed() {
+ self.setNetworkActivityIndicator(false)
+ }
+
+ func announcementsFetched(_ announcements: [Announcement]) { <i class="conum" data-value="2"></i><b>(2)</b>
+ self.setNetworkActivityIndicator(false)
+
+ self.tableViewDataSource.announcements = announcements
+ self.tableViewDelegate.announcements = announcements
+ self.tableView.reloadData()
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1007,13 +1171,43 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDataSource : NSObject, UITableViewDataSource {
+
+ var announcements = [Announcement]()
+
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return announcements.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+
+ let announcement = announcements[indexPath.row]
+ cell.textLabel!.text = announcement.title
+ return cell
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDelegate : NSObject, UITableViewDelegate {
+
+ var announcements = [Announcement]()
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let announcement = announcements[indexPath.row]
+ NotificationCenter.default.post(name: Notification.Name("AnnouncementTappedNotification"), object: announcement) <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1044,7 +1238,15 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }</code></pre>
</div>
</div>
<div class="imageblock">
@@ -1066,7 +1268,61 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+ self.configureView()
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ // Update the view.
+ self.configureView()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+}</code></pre>
</div>
</div>
@@ -1314,7 +1570,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/GrailsApi.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080"
+ static let version = "2.0" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements"
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1334,7 +1596,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcementPrimaryKey = announcement.primaryKey
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -1351,7 +1622,86 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate, AnnouncementFetcherDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ let fetcher = AnnouncementFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.fetcher.delegate = self
+ self.configureView()
+ }
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ self.configureView()
+ }
+ }
+
+ var announcementPrimaryKey: Int? {
+ didSet {
+ self.showActivityIndicator()
+ self.fetcher.fetchAnnouncement(self.announcementPrimaryKey!)
+ }
+ }
+
+ // MARK: - Private Methods
+ func showActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.startAnimating()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+
+ // Mark: - AnnouncementFetcherDelegate
+
+ func announcementFetchingFailed() {
+
+ }
+
+ func announcementFetched(_ announcement: Announcement) {
+ self.announcement = announcement
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1360,7 +1710,42 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate: AnnouncementFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncement(_ primaryKey: Int) {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)/\(primaryKey)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ return
+ }
+ if let announcement = self.builder.announcementFromJSON(data) {
+ if let delegate = self.delegate {
+ delegate.announcementFetched(announcement)
+ }
+ } else {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1369,7 +1754,20 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> func announcementFromJSON(_ data: Data?) -> Announcement? {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ if let dict = json as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ return announcement
+
+ }
+ }
+ return nil
+ }</code></pre>
</div>
</div>
<div class="paragraph">
@@ -1378,7 +1776,12 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementFetcherDelegate: class {
+
+ func announcementFetchingFailed()
+
+ func announcementFetched(_ announcement: Announcement)
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/version2.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/version2.html
index 3f31d4a96bc..54a4e557b30 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/version2.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/version2.html
@@ -338,7 +338,13 @@ endpoint. The next functional test verifies that behaviour.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/GrailsApi.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080"
+ static let version = "2.0" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements"
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -358,7 +364,16 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcementPrimaryKey = announcement.primaryKey
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -375,7 +390,86 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate, AnnouncementFetcherDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ let fetcher = AnnouncementFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.fetcher.delegate = self
+ self.configureView()
+ }
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ self.configureView()
+ }
+ }
+
+ var announcementPrimaryKey: Int? {
+ didSet {
+ self.showActivityIndicator()
+ self.fetcher.fetchAnnouncement(self.announcementPrimaryKey!)
+ }
+ }
+
+ // MARK: - Private Methods
+ func showActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.startAnimating()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+
+ // Mark: - AnnouncementFetcherDelegate
+
+ func announcementFetchingFailed() {
+
+ }
+
+ func announcementFetched(_ announcement: Announcement) {
+ self.announcement = announcement
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -384,7 +478,42 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate: AnnouncementFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncement(_ primaryKey: Int) {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)/\(primaryKey)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ return
+ }
+ if let announcement = self.builder.announcementFromJSON(data) {
+ if let delegate = self.delegate {
+ delegate.announcementFetched(announcement)
+ }
+ } else {
+ if let delegate = self.delegate {
+ delegate.announcementFetchingFailed()
+ }
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -393,7 +522,20 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.m</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> func announcementFromJSON(_ data: Data?) -> Announcement? {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ if let dict = json as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ return announcement
+
+ }
+ }
+ return nil
+ }</code></pre>
</div>
</div>
<div class="paragraph">
@@ -402,7 +544,12 @@ in <em>MasterViewController</em> as illustrated below:</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v2/IntranetClient/AnnouncementFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementFetcherDelegate: class {
+
+ func announcementFetchingFailed()
+
+ func announcementFetched(_ announcement: Announcement)
+}</code></pre>
</div>
</div>
diff --git a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
index 0ff4a250a05..7d039857229 100644
--- a/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
+++ b/guides/building-an-ios-swift-client-powered-by-a-grails-backend/4/guide/writingTheiOSApp.html
@@ -148,7 +148,16 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/Announcement.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/Announcement.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">class Announcement {
+ let primaryKey: Int
+ let title: String
+ var body: String?
+
+ init(primaryKey: Int, title: String) {
+ self.primaryKey = primaryKey
+ self.title = title
+ }
+}</code></pre>
</div>
</div>
@@ -168,7 +177,29 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-v1/IntranetClient/AnnouncementBuilder.h</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementBuilder.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementBuilder {
+
+ func announcementsFromJSON(_ data: Data?) -> [Announcement] {
+ let json = try? JSONSerialization.jsonObject(with: data!, options: [])
+ var announcements = [Announcement]()
+ if let array = json as? [Any] {
+ for object in array {
+ if let dict = object as? Dictionary<String, AnyObject> {
+ if let title = dict["title"] as? String, let primaryKey = dict["id"] as? Int {
+ let announcement = Announcement(primaryKey: primaryKey, title: title)
+ if let body = dict["body"] as? String {
+ announcement.body = body
+ }
+ announcements.append(announcement)
+ }
+ }
+ }
+ }
+ return announcements
+ }
+}</code></pre>
</div>
</div>
@@ -188,7 +219,13 @@ announcements exposed by the Grails application.</p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/GrailsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/GrailsApi.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">struct GrailsApi {
+ static let serverUrl = "http://192.168.1.40:8080" <i class="conum" data-value="1"></i><b>(1)</b>
+ struct Announcements {
+ static let Path = "announcements" <i class="conum" data-value="2"></i><b>(2)</b>
+ }
+ static let version = "1.0" <i class="conum" data-value="3"></i><b>(3)</b>
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -222,7 +259,37 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcher.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import Foundation
+
+class AnnouncementsFetcher : NSObject, URLSessionDelegate {
+
+ weak var delegate:AnnouncementsFetcherDelegate?
+ let builder = AnnouncementBuilder()
+
+ func fetchAnnouncements() {
+ let url = URL(string: "\(GrailsApi.serverUrl)/\(GrailsApi.Announcements.Path)")!
+ let req = NSMutableURLRequest(url:url)
+ req.setValue(GrailsApi.version, forHTTPHeaderField: "Accept-Version") <i class="conum" data-value="1"></i><b>(1)</b>
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+
+ let task = session.dataTask(with: req as URLRequest) { (data, response, error) in
+ if error != nil {
+ if let delegate = self.delegate {
+ delegate.announcementsFetchingFailed()
+ }
+ return
+ }
+ let announcements = self.builder.announcementsFromJSON(data)
+ if let delegate = self.delegate {
+ delegate.announcementsFetched(announcements)
+ }
+
+ }
+ task.resume()
+
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -239,7 +306,12 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsFetcherDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">protocol AnnouncementsFetcherDelegate: class {
+
+ func announcementsFetchingFailed()
+
+ func announcementsFetched(_ announcements: [Announcement])
+}</code></pre>
</div>
</div>
<div class="paragraph">
@@ -248,7 +320,99 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class MasterViewController: UITableViewController, AnnouncementsFetcherDelegate {
+
+ var detailViewController: DetailViewController? = nil
+ var objects = [Any]()
+
+ var tableViewDataSource = AnnouncementsTableViewDataSource()
+ var tableViewDelegate = AnnouncementsTableViewDelegate()
+ let fetcher = AnnouncementsFetcher()
+
+ // MARK: - LifeCycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.tableView.dataSource = self.tableViewDataSource
+ self.tableView.delegate = self.tableViewDelegate
+ self.fetcher.delegate = self
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
+ super.viewWillAppear(animated)
+ self.registerNotifications()
+ self.fetchAnnouncements() <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ self.unregisterNotifications()
+ }
+
+ // MARK: - Notifications
+
+ func registerNotifications() {
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.announcementTapped),
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ func unregisterNotifications() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: Notification.Name("AnnouncementTappedNotification"),
+ object: nil)
+ }
+
+ // MARK: - Private Methods
+
+ @objc func announcementTapped(notification: NSNotification){
+
+ let announcement = notification.object as! Announcement
+ self.performSegue(withIdentifier: "showDetail", sender: announcement)
+ }
+
+ func fetchAnnouncements() {
+ self.setNetworkActivityIndicator(true)
+ self.fetcher.fetchAnnouncements()
+ }
+
+ func setNetworkActivityIndicator(_ visible: Bool) {
+ UIApplication.shared.isNetworkActivityIndicatorVisible = visible
+ }
+
+ // MARK: - Segues
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }
+ }
+
+ // MARK: - AnnouncementsFetcherDelegate
+
+ func announcementsFetchingFailed() {
+ self.setNetworkActivityIndicator(false)
+ }
+
+ func announcementsFetched(_ announcements: [Announcement]) { <i class="conum" data-value="2"></i><b>(2)</b>
+ self.setNetworkActivityIndicator(false)
+
+ self.tableViewDataSource.announcements = announcements
+ self.tableViewDelegate.announcements = announcements
+ self.tableView.reloadData()
+ }
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -269,13 +433,43 @@ You may need to change the ip address to match your local machine.
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDataSource.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDataSource : NSObject, UITableViewDataSource {
+
+ var announcements = [Announcement]()
+
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return announcements.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+
+ let announcement = announcements[indexPath.row]
+ cell.textLabel!.text = announcement.title
+ return cell
+ }
+}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/AnnouncementsTableViewDelegate.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class AnnouncementsTableViewDelegate : NSObject, UITableViewDelegate {
+
+ var announcements = [Announcement]()
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let announcement = announcements[indexPath.row]
+ NotificationCenter.default.post(name: Notification.Name("AnnouncementTappedNotification"), object: announcement) <i class="conum" data-value="1"></i><b>(1)</b>
+ }
+
+}</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -306,7 +500,15 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/MasterViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/MasterViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift"> override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ if segue.identifier == "showDetail" {
+ let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
+ if let announcement = sender as? Announcement {
+ controller.announcement = announcement
+ }
+ controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
+ controller.navigationItem.leftItemsSupplementBackButton = true
+ }</code></pre>
</div>
</div>
<div class="imageblock">
@@ -328,7 +530,61 @@ we set the announcement property of <em>DetailViewController</em></p>
<div class="listingblock">
<div class="title">/complete-swift-ios-v1/IntranetClient/DetailViewController.swift</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="swift">link:{sourcedir}/../complete-swift-ios-v1/IntranetClient/DetailViewController.swift[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="swift">import UIKit
+
+class DetailViewController: UIViewController, UIWebViewDelegate {
+
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var webView: UIWebView!
+ @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
+
+ func configureView() {
+ // Update the user interface for the detail item.
+ if let announcement = self.announcement {
+ if let label = self.titleLabel {
+ label.text = announcement.title
+ }
+ if let webView = self.webView, let body = announcement.body {
+ self.hideActivityIndicator()
+ webView.loadHTMLString(body, baseURL: nil)
+ } else {
+ self.hideActivityIndicator()
+ }
+ } else {
+ self.hideActivityIndicator()
+ }
+ }
+
+ func hideActivityIndicator() {
+ if let activityIndicatorView = self.activityIndicatorView {
+ activityIndicatorView.stopAnimating()
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+ self.configureView()
+ }
+
+ var announcement: Announcement? {
+ didSet {
+ // Update the view.
+ self.configureView()
+ }
+ }
+
+
+ // MARK: - UIWebViewDelegate
+
+ func webViewDidFinishLoad(_ webView: UIWebView) {
+ self.hideActivityIndicator()
+ }
+
+ func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
+ self.hideActivityIndicator()
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/discriminator-per-tenant/4/guide/index.html b/guides/discriminator-per-tenant/4/guide/index.html
index d4ad0466a93..a496c66d1fa 100644
--- a/guides/discriminator-per-tenant/4/guide/index.html
+++ b/guides/discriminator-per-tenant/4/guide/index.html
@@ -548,7 +548,22 @@ to the two tenants supported by this application.</p>
<div class="listingblock">
<div class="title">grails-app/controllers/example/ManufacturerService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/ManufacturerService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(Manufacturer)
+<span class="type">interface</span> ManufacturerService {
+
+ <span class="type">int</span> count()
+
+ Manufacturer save(<span class="predefined-type">String</span> name)
+
+ <span class="predefined-type">List</span><Manufacturer> findAll()
+
+ Manufacturer findByName(<span class="predefined-type">String</span> name)
+
+}</code></pre>
</div>
</div>
<div class="paragraph">
diff --git a/guides/discriminator-per-tenant/4/guide/pages/tenantSelection.html b/guides/discriminator-per-tenant/4/guide/pages/tenantSelection.html
index a5d7af8507d..0c3d3d4de9a 100644
--- a/guides/discriminator-per-tenant/4/guide/pages/tenantSelection.html
+++ b/guides/discriminator-per-tenant/4/guide/pages/tenantSelection.html
@@ -99,7 +99,22 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/controllers/example/ManufacturerService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/ManufacturerService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(Manufacturer)
+<span class="type">interface</span> ManufacturerService {
+
+ <span class="type">int</span> count()
+
+ Manufacturer save(<span class="predefined-type">String</span> name)
+
+ <span class="predefined-type">List</span><Manufacturer> findAll()
+
+ Manufacturer findByName(<span class="predefined-type">String</span> name)
+
+}</code></pre>
</div>
</div>
<div class="paragraph">
diff --git a/guides/discriminator-per-tenant/4/guide/single.html b/guides/discriminator-per-tenant/4/guide/single.html
index d4ad0466a93..a496c66d1fa 100644
--- a/guides/discriminator-per-tenant/4/guide/single.html
+++ b/guides/discriminator-per-tenant/4/guide/single.html
@@ -548,7 +548,22 @@ to the two tenants supported by this application.</p>
<div class="listingblock">
<div class="title">grails-app/controllers/example/ManufacturerService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/ManufacturerService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(Manufacturer)
+<span class="type">interface</span> ManufacturerService {
+
+ <span class="type">int</span> count()
+
+ Manufacturer save(<span class="predefined-type">String</span> name)
+
+ <span class="predefined-type">List</span><Manufacturer> findAll()
+
+ Manufacturer findByName(<span class="predefined-type">String</span> name)
+
+}</code></pre>
</div>
</div>
<div class="paragraph">
diff --git a/guides/discriminator-per-tenant/4/guide/writingTheApp.html b/guides/discriminator-per-tenant/4/guide/writingTheApp.html
index 38e7a14d34e..117e3e4fb20 100644
--- a/guides/discriminator-per-tenant/4/guide/writingTheApp.html
+++ b/guides/discriminator-per-tenant/4/guide/writingTheApp.html
@@ -374,7 +374,22 @@ to the two tenants supported by this application.</p>
<div class="listingblock">
<div class="title">grails-app/controllers/example/ManufacturerService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/ManufacturerService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(Manufacturer)
+<span class="type">interface</span> ManufacturerService {
+
+ <span class="type">int</span> count()
+
+ Manufacturer save(<span class="predefined-type">String</span> name)
+
+ <span class="predefined-type">List</span><Manufacturer> findAll()
+
+ Manufacturer findByName(<span class="predefined-type">String</span> name)
+
+}</code></pre>
</div>
</div>
<div class="paragraph">
diff --git a/guides/grails-basicauth/3/guide/index.html b/guides/grails-basicauth/3/guide/index.html
index b38fda38ebe..bcc7f8168f3 100644
--- a/guides/grails-basicauth/3/guide/index.html
+++ b/guides/grails-basicauth/3/guide/index.html
@@ -290,7 +290,7 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">compile <span class="string"><span class="delimiter">'</span><span class="content">org.grails.plugins:spring-security-core:3.2.1</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -337,7 +337,7 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/conf/application.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/conf/application.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">grails.plugin.springsecurity.useBasicAuth = <span class="predefined-constant">true</span></code></pre>
</div>
</div>
@@ -357,7 +357,39 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/controllers/example/grails/HomeController.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/controllers/example/grails/HomeController.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">groovy.transform.CompileStatic</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.SpringSecurityService</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.annotation.Secured</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.userdetails.GrailsUser</span>
+
+<span class="annotation">@CompileStatic</span>
+<span class="annotation">@Secured</span>(<span class="string"><span class="delimiter">'</span><span class="content">isAuthenticated()</span><span class="delimiter">'</span></span>)
+<span class="type">class</span> <span class="class">HomeController</span> {
+
+ <span class="directive">static</span> responseFormats = [<span class="string"><span class="delimiter">'</span><span class="content">json</span><span class="delimiter">'</span></span>]
+
+ SpringSecurityService springSecurityService
+
+ <span class="keyword">def</span> <span class="function">index</span>() {
+ [<span class="key">name</span>: loggedUsername()]
+ }
+
+ <span class="predefined-type">String</span> loggedUsername() {
+ <span class="keyword">if</span> ( springSecurityService.principal == <span class="predefined-constant">null</span> ) {
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> <span class="predefined-type">String</span> ) {
+ <span class="keyword">return</span> springSecurityService.principal
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> GrailsUser ) {
+ <span class="keyword">return</span> ((GrailsUser) springSecurityService.principal).username
+ }
+ <span class="predefined-constant">null</span>
+ }
+
+}</code></pre>
</div>
</div>
@@ -377,7 +409,13 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/views/home/index.gson</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/views/home/index.gson[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">model {
+ <span class="predefined-type">String</span> name
+}
+
+json {
+ username name
+}</code></pre>
</div>
</div>
@@ -404,7 +442,19 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/services/example/grails/UserDataService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/grails/UserDataService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(User)
+<span class="type">interface</span> UserDataService {
+
+ User save(<span class="predefined-type">String</span> username, <span class="predefined-type">String</span> password, <span class="type">boolean</span> enabled, <span class="type">boolean</span> accountExpired, <span class="type">boolean</span> accountLocked, <span class="type">boolean</span> passwordExpired)
+
+ <span class="type">void</span> delete(<span class="predefined-type">Serializable</span> id)
+
+ <span class="type">int</span> count()
+}</code></pre>
</div>
</div>
@@ -424,7 +474,46 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">src/integration-test/groovy/example/grails/HomeControllerSpec.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/src/integration-test/groovy/example/grails/HomeControllerSpec.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestBuilder</span>
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestResponse</span>
+<span class="keyword">import</span> <span class="include">grails.testing.mixin.integration.Integration</span>
+<span class="keyword">import</span> <span class="include">spock.lang.Specification</span>
+
+<span class="annotation">@Integration</span>
+<span class="type">class</span> <span class="class">HomeControllerSpec</span> <span class="directive">extends</span> Specification {
+
+ UserDataService userDataService
+
+ <span class="keyword">def</span> <span class="string"><span class="delimiter">"</span><span class="content">verify basic auth is possible</span><span class="delimiter">"</span></span>() {
+ <span class="key">given</span>:
+ RestBuilder rest = <span class="keyword">new</span> RestBuilder()
+ <span class="directive">final</span> <span class="predefined-type">String</span> username = <span class="string"><span class="delimiter">'</span><span class="content">sherlock</span><span class="delimiter">'</span></span>
+ <span class="directive">final</span> <span class="predefined-type">String</span> password = <span class="string"><span class="delimiter">'</span><span class="content">elementary</span><span class="delimiter">'</span></span>
+
+ <span class="key">when</span>:
+ User user = userDataService.save(username, password, <span class="predefined-constant">true</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, )
+
+ <span class="key">then</span>:
+ userDataService.count() == old(userDataService.count()) + <span class="integer">1</span>
+
+ <span class="key">when</span>:
+ <span class="predefined-type">String</span> token = <span class="string"><span class="delimiter">"</span><span class="inline"><span class="inline-delimiter">${</span>username<span class="inline-delimiter">}</span></span><span class="content">:</span><span class="inline"><span class="inline-delimiter">${</span>password<span class="inline-delimiter">}</span></span><span class="delimiter">"</span></span>.encodeAsBase64()
+ RestResponse rsp = rest.get(<span class="string"><span class="delimiter">"</span><span class="content">http://localhost:</span><span class="inline"><span class="inline-delimiter">${</span>serverPort<span class="inline-delimiter">}</span></span><span class="content">/home</span><span class="delimiter">"</span></span>) {
+ auth(<span class="string"><span class="delimiter">"</span><span class="content">Basic </span><span class="inline"><span class="inline-delimiter">$</span>token</span><span class="delimiter">"</span></span>)
+ }
+
+ <span class="key">then</span>:
+ rsp.status == <span class="integer">200</span>
+
+ <span class="key">and</span>:
+ rsp.json.username == username
+
+ <span class="key">cleanup</span>:
+ userDataService?.delete(user?.id)
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/pages/controller.html b/guides/grails-basicauth/3/guide/pages/controller.html
index 7fe488ff0c3..ad875a03703 100644
--- a/guides/grails-basicauth/3/guide/pages/controller.html
+++ b/guides/grails-basicauth/3/guide/pages/controller.html
@@ -81,7 +81,39 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/controllers/example/grails/HomeController.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/controllers/example/grails/HomeController.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">groovy.transform.CompileStatic</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.SpringSecurityService</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.annotation.Secured</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.userdetails.GrailsUser</span>
+
+<span class="annotation">@CompileStatic</span>
+<span class="annotation">@Secured</span>(<span class="string"><span class="delimiter">'</span><span class="content">isAuthenticated()</span><span class="delimiter">'</span></span>)
+<span class="type">class</span> <span class="class">HomeController</span> {
+
+ <span class="directive">static</span> responseFormats = [<span class="string"><span class="delimiter">'</span><span class="content">json</span><span class="delimiter">'</span></span>]
+
+ SpringSecurityService springSecurityService
+
+ <span class="keyword">def</span> <span class="function">index</span>() {
+ [<span class="key">name</span>: loggedUsername()]
+ }
+
+ <span class="predefined-type">String</span> loggedUsername() {
+ <span class="keyword">if</span> ( springSecurityService.principal == <span class="predefined-constant">null</span> ) {
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> <span class="predefined-type">String</span> ) {
+ <span class="keyword">return</span> springSecurityService.principal
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> GrailsUser ) {
+ <span class="keyword">return</span> ((GrailsUser) springSecurityService.principal).username
+ }
+ <span class="predefined-constant">null</span>
+ }
+
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/pages/dependencies.html b/guides/grails-basicauth/3/guide/pages/dependencies.html
index a7af27d834f..f633c98635c 100644
--- a/guides/grails-basicauth/3/guide/pages/dependencies.html
+++ b/guides/grails-basicauth/3/guide/pages/dependencies.html
@@ -81,7 +81,7 @@ function addJsClass() {
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">compile <span class="string"><span class="delimiter">'</span><span class="content">org.grails.plugins:spring-security-core:3.2.1</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -128,7 +128,7 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/conf/application.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/conf/application.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">grails.plugin.springsecurity.useBasicAuth = <span class="predefined-constant">true</span></code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/pages/services.html b/guides/grails-basicauth/3/guide/pages/services.html
index 74c0067396f..981b5b9bd98 100644
--- a/guides/grails-basicauth/3/guide/pages/services.html
+++ b/guides/grails-basicauth/3/guide/pages/services.html
@@ -88,7 +88,19 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/services/example/grails/UserDataService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/grails/UserDataService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(User)
+<span class="type">interface</span> UserDataService {
+
+ User save(<span class="predefined-type">String</span> username, <span class="predefined-type">String</span> password, <span class="type">boolean</span> enabled, <span class="type">boolean</span> accountExpired, <span class="type">boolean</span> accountLocked, <span class="type">boolean</span> passwordExpired)
+
+ <span class="type">void</span> delete(<span class="predefined-type">Serializable</span> id)
+
+ <span class="type">int</span> count()
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/pages/test.html b/guides/grails-basicauth/3/guide/pages/test.html
index c14e97e57fd..4a6d330e1d9 100644
--- a/guides/grails-basicauth/3/guide/pages/test.html
+++ b/guides/grails-basicauth/3/guide/pages/test.html
@@ -81,7 +81,46 @@ function addJsClass() {
<div class="listingblock">
<div class="title">src/integration-test/groovy/example/grails/HomeControllerSpec.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/src/integration-test/groovy/example/grails/HomeControllerSpec.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestBuilder</span>
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestResponse</span>
+<span class="keyword">import</span> <span class="include">grails.testing.mixin.integration.Integration</span>
+<span class="keyword">import</span> <span class="include">spock.lang.Specification</span>
+
+<span class="annotation">@Integration</span>
+<span class="type">class</span> <span class="class">HomeControllerSpec</span> <span class="directive">extends</span> Specification {
+
+ UserDataService userDataService
+
+ <span class="keyword">def</span> <span class="string"><span class="delimiter">"</span><span class="content">verify basic auth is possible</span><span class="delimiter">"</span></span>() {
+ <span class="key">given</span>:
+ RestBuilder rest = <span class="keyword">new</span> RestBuilder()
+ <span class="directive">final</span> <span class="predefined-type">String</span> username = <span class="string"><span class="delimiter">'</span><span class="content">sherlock</span><span class="delimiter">'</span></span>
+ <span class="directive">final</span> <span class="predefined-type">String</span> password = <span class="string"><span class="delimiter">'</span><span class="content">elementary</span><span class="delimiter">'</span></span>
+
+ <span class="key">when</span>:
+ User user = userDataService.save(username, password, <span class="predefined-constant">true</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, )
+
+ <span class="key">then</span>:
+ userDataService.count() == old(userDataService.count()) + <span class="integer">1</span>
+
+ <span class="key">when</span>:
+ <span class="predefined-type">String</span> token = <span class="string"><span class="delimiter">"</span><span class="inline"><span class="inline-delimiter">${</span>username<span class="inline-delimiter">}</span></span><span class="content">:</span><span class="inline"><span class="inline-delimiter">${</span>password<span class="inline-delimiter">}</span></span><span class="delimiter">"</span></span>.encodeAsBase64()
+ RestResponse rsp = rest.get(<span class="string"><span class="delimiter">"</span><span class="content">http://localhost:</span><span class="inline"><span class="inline-delimiter">${</span>serverPort<span class="inline-delimiter">}</span></span><span class="content">/home</span><span class="delimiter">"</span></span>) {
+ auth(<span class="string"><span class="delimiter">"</span><span class="content">Basic </span><span class="inline"><span class="inline-delimiter">$</span>token</span><span class="delimiter">"</span></span>)
+ }
+
+ <span class="key">then</span>:
+ rsp.status == <span class="integer">200</span>
+
+ <span class="key">and</span>:
+ rsp.json.username == username
+
+ <span class="key">cleanup</span>:
+ userDataService?.delete(user?.id)
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/pages/views.html b/guides/grails-basicauth/3/guide/pages/views.html
index 71fe817ba92..1aca49d7996 100644
--- a/guides/grails-basicauth/3/guide/pages/views.html
+++ b/guides/grails-basicauth/3/guide/pages/views.html
@@ -81,7 +81,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/views/home/index.gson</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/views/home/index.gson[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">model {
+ <span class="predefined-type">String</span> name
+}
+
+json {
+ username name
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/single.html b/guides/grails-basicauth/3/guide/single.html
index b38fda38ebe..bcc7f8168f3 100644
--- a/guides/grails-basicauth/3/guide/single.html
+++ b/guides/grails-basicauth/3/guide/single.html
@@ -290,7 +290,7 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">compile <span class="string"><span class="delimiter">'</span><span class="content">org.grails.plugins:spring-security-core:3.2.1</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -337,7 +337,7 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/conf/application.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/conf/application.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">grails.plugin.springsecurity.useBasicAuth = <span class="predefined-constant">true</span></code></pre>
</div>
</div>
@@ -357,7 +357,39 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/controllers/example/grails/HomeController.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/controllers/example/grails/HomeController.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">groovy.transform.CompileStatic</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.SpringSecurityService</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.annotation.Secured</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.userdetails.GrailsUser</span>
+
+<span class="annotation">@CompileStatic</span>
+<span class="annotation">@Secured</span>(<span class="string"><span class="delimiter">'</span><span class="content">isAuthenticated()</span><span class="delimiter">'</span></span>)
+<span class="type">class</span> <span class="class">HomeController</span> {
+
+ <span class="directive">static</span> responseFormats = [<span class="string"><span class="delimiter">'</span><span class="content">json</span><span class="delimiter">'</span></span>]
+
+ SpringSecurityService springSecurityService
+
+ <span class="keyword">def</span> <span class="function">index</span>() {
+ [<span class="key">name</span>: loggedUsername()]
+ }
+
+ <span class="predefined-type">String</span> loggedUsername() {
+ <span class="keyword">if</span> ( springSecurityService.principal == <span class="predefined-constant">null</span> ) {
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> <span class="predefined-type">String</span> ) {
+ <span class="keyword">return</span> springSecurityService.principal
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> GrailsUser ) {
+ <span class="keyword">return</span> ((GrailsUser) springSecurityService.principal).username
+ }
+ <span class="predefined-constant">null</span>
+ }
+
+}</code></pre>
</div>
</div>
@@ -377,7 +409,13 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/views/home/index.gson</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/views/home/index.gson[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">model {
+ <span class="predefined-type">String</span> name
+}
+
+json {
+ username name
+}</code></pre>
</div>
</div>
@@ -404,7 +442,19 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">grails-app/services/example/grails/UserDataService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/grails/UserDataService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(User)
+<span class="type">interface</span> UserDataService {
+
+ User save(<span class="predefined-type">String</span> username, <span class="predefined-type">String</span> password, <span class="type">boolean</span> enabled, <span class="type">boolean</span> accountExpired, <span class="type">boolean</span> accountLocked, <span class="type">boolean</span> passwordExpired)
+
+ <span class="type">void</span> delete(<span class="predefined-type">Serializable</span> id)
+
+ <span class="type">int</span> count()
+}</code></pre>
</div>
</div>
@@ -424,7 +474,46 @@ You can go right to the <strong>completed example</strong> if you <code>cd</code
<div class="listingblock">
<div class="title">src/integration-test/groovy/example/grails/HomeControllerSpec.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/src/integration-test/groovy/example/grails/HomeControllerSpec.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestBuilder</span>
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestResponse</span>
+<span class="keyword">import</span> <span class="include">grails.testing.mixin.integration.Integration</span>
+<span class="keyword">import</span> <span class="include">spock.lang.Specification</span>
+
+<span class="annotation">@Integration</span>
+<span class="type">class</span> <span class="class">HomeControllerSpec</span> <span class="directive">extends</span> Specification {
+
+ UserDataService userDataService
+
+ <span class="keyword">def</span> <span class="string"><span class="delimiter">"</span><span class="content">verify basic auth is possible</span><span class="delimiter">"</span></span>() {
+ <span class="key">given</span>:
+ RestBuilder rest = <span class="keyword">new</span> RestBuilder()
+ <span class="directive">final</span> <span class="predefined-type">String</span> username = <span class="string"><span class="delimiter">'</span><span class="content">sherlock</span><span class="delimiter">'</span></span>
+ <span class="directive">final</span> <span class="predefined-type">String</span> password = <span class="string"><span class="delimiter">'</span><span class="content">elementary</span><span class="delimiter">'</span></span>
+
+ <span class="key">when</span>:
+ User user = userDataService.save(username, password, <span class="predefined-constant">true</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, )
+
+ <span class="key">then</span>:
+ userDataService.count() == old(userDataService.count()) + <span class="integer">1</span>
+
+ <span class="key">when</span>:
+ <span class="predefined-type">String</span> token = <span class="string"><span class="delimiter">"</span><span class="inline"><span class="inline-delimiter">${</span>username<span class="inline-delimiter">}</span></span><span class="content">:</span><span class="inline"><span class="inline-delimiter">${</span>password<span class="inline-delimiter">}</span></span><span class="delimiter">"</span></span>.encodeAsBase64()
+ RestResponse rsp = rest.get(<span class="string"><span class="delimiter">"</span><span class="content">http://localhost:</span><span class="inline"><span class="inline-delimiter">${</span>serverPort<span class="inline-delimiter">}</span></span><span class="content">/home</span><span class="delimiter">"</span></span>) {
+ auth(<span class="string"><span class="delimiter">"</span><span class="content">Basic </span><span class="inline"><span class="inline-delimiter">$</span>token</span><span class="delimiter">"</span></span>)
+ }
+
+ <span class="key">then</span>:
+ rsp.status == <span class="integer">200</span>
+
+ <span class="key">and</span>:
+ rsp.json.username == username
+
+ <span class="key">cleanup</span>:
+ userDataService?.delete(user?.id)
+ }
+}</code></pre>
</div>
</div>
diff --git a/guides/grails-basicauth/3/guide/writingTheApp.html b/guides/grails-basicauth/3/guide/writingTheApp.html
index 0589f4d99c8..922ae6c2873 100644
--- a/guides/grails-basicauth/3/guide/writingTheApp.html
+++ b/guides/grails-basicauth/3/guide/writingTheApp.html
@@ -121,7 +121,7 @@ function addJsClass() {
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/build.gradle[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">compile <span class="string"><span class="delimiter">'</span><span class="content">org.grails.plugins:spring-security-core:3.2.1</span><span class="delimiter">'</span></span></code></pre>
</div>
</div>
<div class="paragraph">
@@ -168,7 +168,7 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/conf/application.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/conf/application.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">grails.plugin.springsecurity.useBasicAuth = <span class="predefined-constant">true</span></code></pre>
</div>
</div>
@@ -188,7 +188,39 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/controllers/example/grails/HomeController.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/controllers/example/grails/HomeController.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">groovy.transform.CompileStatic</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.SpringSecurityService</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.annotation.Secured</span>
+<span class="keyword">import</span> <span class="include">grails.plugin.springsecurity.userdetails.GrailsUser</span>
+
+<span class="annotation">@CompileStatic</span>
+<span class="annotation">@Secured</span>(<span class="string"><span class="delimiter">'</span><span class="content">isAuthenticated()</span><span class="delimiter">'</span></span>)
+<span class="type">class</span> <span class="class">HomeController</span> {
+
+ <span class="directive">static</span> responseFormats = [<span class="string"><span class="delimiter">'</span><span class="content">json</span><span class="delimiter">'</span></span>]
+
+ SpringSecurityService springSecurityService
+
+ <span class="keyword">def</span> <span class="function">index</span>() {
+ [<span class="key">name</span>: loggedUsername()]
+ }
+
+ <span class="predefined-type">String</span> loggedUsername() {
+ <span class="keyword">if</span> ( springSecurityService.principal == <span class="predefined-constant">null</span> ) {
+ <span class="keyword">return</span> <span class="predefined-constant">null</span>
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> <span class="predefined-type">String</span> ) {
+ <span class="keyword">return</span> springSecurityService.principal
+ }
+ <span class="keyword">if</span> ( springSecurityService.principal <span class="keyword">instanceof</span> GrailsUser ) {
+ <span class="keyword">return</span> ((GrailsUser) springSecurityService.principal).username
+ }
+ <span class="predefined-constant">null</span>
+ }
+
+}</code></pre>
</div>
</div>
@@ -208,7 +240,13 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/views/home/index.gson</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/views/home/index.gson[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy">model {
+ <span class="predefined-type">String</span> name
+}
+
+json {
+ username name
+}</code></pre>
</div>
</div>
@@ -235,7 +273,19 @@ function addJsClass() {
<div class="listingblock">
<div class="title">grails-app/services/example/grails/UserDataService.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/grails-app/services/example/grails/UserDataService.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.gorm.services.Service</span>
+
+<span class="annotation">@Service</span>(User)
+<span class="type">interface</span> UserDataService {
+
+ User save(<span class="predefined-type">String</span> username, <span class="predefined-type">String</span> password, <span class="type">boolean</span> enabled, <span class="type">boolean</span> accountExpired, <span class="type">boolean</span> accountLocked, <span class="type">boolean</span> passwordExpired)
+
+ <span class="type">void</span> delete(<span class="predefined-type">Serializable</span> id)
+
+ <span class="type">int</span> count()
+}</code></pre>
</div>
</div>
@@ -255,7 +305,46 @@ function addJsClass() {
<div class="listingblock">
<div class="title">src/integration-test/groovy/example/grails/HomeControllerSpec.groovy</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="groovy"><span class="key">link</span>:..<span class="regexp"><span class="delimiter">/</span><span class="content">..</span><span class="delimiter">/</span></span>snippets/src/integration-test/groovy/example/grails/HomeControllerSpec.groovy[role=include]</code></pre>
+<pre class="CodeRay highlight"><code data-lang="groovy"><span class="keyword">package</span> example.grails
+
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestBuilder</span>
+<span class="keyword">import</span> <span class="include">grails.plugins.rest.client.RestResponse</span>
+<span class="keyword">import</span> <span class="include">grails.testing.mixin.integration.Integration</span>
+<span class="keyword">import</span> <span class="include">spock.lang.Specification</span>
+
+<span class="annotation">@Integration</span>
+<span class="type">class</span> <span class="class">HomeControllerSpec</span> <span class="directive">extends</span> Specification {
+
+ UserDataService userDataService
+
+ <span class="keyword">def</span> <span class="string"><span class="delimiter">"</span><span class="content">verify basic auth is possible</span><span class="delimiter">"</span></span>() {
+ <span class="key">given</span>:
+ RestBuilder rest = <span class="keyword">new</span> RestBuilder()
+ <span class="directive">final</span> <span class="predefined-type">String</span> username = <span class="string"><span class="delimiter">'</span><span class="content">sherlock</span><span class="delimiter">'</span></span>
+ <span class="directive">final</span> <span class="predefined-type">String</span> password = <span class="string"><span class="delimiter">'</span><span class="content">elementary</span><span class="delimiter">'</span></span>
+
+ <span class="key">when</span>:
+ User user = userDataService.save(username, password, <span class="predefined-constant">true</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, <span class="predefined-constant">false</span>, )
... 10052 lines suppressed ...