tag:blogger.com,1999:blog-31068533293265256742024-03-19T03:43:07.809-07:00chat & codeCode is craft and collaboration is key to success. I love chatting the latest tech trends at coffee break: female geek.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.comBlogger80125tag:blogger.com,1999:blog-3106853329326525674.post-12275542440044581852019-01-18T02:33:00.000-08:002019-01-18T02:33:16.642-08:00FaaS tutorial 2: Set up Google Cloud FunctionNow that we have deployed an app in <a href="http://corinnekrych.blogspot.com/2019/01/faas-tutorial-1-start-with-firebase-and.html">FaaS tutorial 1: Start with Firebase and prepare the ground</a>, time to spice up 🌶️ our basic app to add some back-end stuff.
<br/><br/>
What about defining a REST API to add a new record to the database. We'll use HTTP triggered functions. There are different kind of triggers for different use cases, we dig into that in the next post.
<br/><br/>
Let's start out tutorial, as always step by steps 👣!
<br/><br/>
<h2>Step 1: init function</h2>
For your project to use Google Cloud Functions (GCF), use firebase cli to configure it. Simply run the command:
<pre><code class="language-JavaScript">$ firebase init functions
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
/Users/corinne/workspace/test-crud2
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Select a default Firebase project for this directory: test-83c1a (test)
i Using project test-83c1a (test)
=== Functions Setup
A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
✔ Wrote functions/package.json
✔ Wrote functions/index.js
✔ Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes
</code></pre>
Below the ascii art 🎨 Firebase gets chatty and tells you all about it's doing.
<br/>
Once you've selected a firebase project (select the one we created in tutorial 1 with the firestore setup), you use default options (JavaScript, no ESLint).
<br/><br/>
<div style="border-style: solid;border-color:lightgrey;background: lightgrey;padding:1em"><strong>Note:</strong>
By default, GCF runs on node6, if you want to enable node8, in your <code>/functions/package.json</code> add the following json at root level:
<pre><code class="language-JavaScript">"engines": {
"node": "8"
}
</code></pre>
You will need node8 for the rest of the tutorial as we use <code>async</code> instead of Promises syntax.
</div>
Firebase has created a default package with an initial GCF bootstrap in <code>functions/index.js</code>.
<br/><br/>
<h2>Step 2: HelloWorld</h2>
Go to <code>functions/index.js</code> and uncomment the helloWorld function
<pre><code class="language-JavaScript">exports.helloWorld = functions.https.onCall((request, response) => {
response.send("Hello from Firebase!");
});
</code></pre>
This is a basic helloWorld function, we'll use just to get use to deploying functions.
<br/><br/>
<h2>Step 3: deploy</h2>
Again, use firebase cli and type the command:
<pre><code class="language-JavaScript">$ firebase deploy --only functions
✔ functions[helloWorld(us-central1)]: Successful update operation.
✔ Deploy complete!
Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/test-83c1a/overview
</code></pre>
Note you call also deploy just our functions by adding <code>firebase deploy --only functions:myFunctionName</code>.
<br/>
If you go to the Firebase console and then in the function tab, you will find the URL where your function is available.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikRPb6jDI7zC4oZprsTu1Vi41BNGFe3FRixQc18TwiVypmb2sBeN81Ml7g0DYuiF2idM-yil_6yXYiWeF78mywa_Un8Mz8lhaTufN98QZkw4xgmQ8EAxThlFqEDbmtF4Rq4RlHxob0Sow/s1600/helloWorld-functions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikRPb6jDI7zC4oZprsTu1Vi41BNGFe3FRixQc18TwiVypmb2sBeN81Ml7g0DYuiF2idM-yil_6yXYiWeF78mywa_Un8Mz8lhaTufN98QZkw4xgmQ8EAxThlFqEDbmtF4Rq4RlHxob0Sow/s640/helloWorld-functions.png" width="640" height="193" data-original-width="1240" data-original-height="374" /></a></div>
<br/><br/>
<h2>Step 3: try it</h2>
Since it's an HTTP triggered function, let's trying with <code>curl</code>:
<pre><code class="language-JavaScript">$ curl https://us-central1-test-83c1a.cloudfunctions.net/helloWorld
Hello from Firebase!
</code></pre>
You've deployed and tried your first cloud function. 🎉🎉🎉
<br/>
Let's now try to fulfil the same use-case as per tutorial 1: We want an HTTP triggered function than insert 2 fields in a database collection.
<br/><br/>
<h2>Step 4: onRequest function to insert in DB</h2>
<ul>
<li> In <code>function/index.js</code> add the function below:
<pre><code class="language-JavaScript">const admin = require('firebase-admin');
admin.initializeApp(); // [2]
exports.insertOnRequest = functions.https.onRequest(async (req, res) => {
const field1 = req.query.field1; // [2]
const field2 = req.query.field2;
const writeResult = await admin.firestore().collection('items').add({field1: field1, field2: field2}); // [3]
res.json({result: `Message with ID: ${writeResult.id} added.`}); // [4]
});
</code></pre>
<br>
<ul>
<li>[1]: import the Firebase Admin SDK to access the Firestore database and initialize with default values.</li>
<li>[2]: extract data from query param.</li>
<li>[3]: add the new message into the Firestore Database using the Firebase Admin SDK.</li>
<li>[4]: send back the id of the newly inserted record.</li>
</ul>
</li>
<li>Deploy it with <code>firebase deploy --only functions</code>. This will redeploy both functions.</li>
<li> Test it by curling:
<pre><code class="language-JavaScript">$ curl https://us-central1-test-83c1a.cloudfunctions.net/insertOnRequest\?field1\=test1\&field2\=test2
{"result":"Message with ID: b5Nw8U3wraQhRqJ0vMER added."}
</code></pre>
</li>
</ul>
Wow! Even better, you've deployed a cloud function that does something 🎉🎉🎉
<br/><br/>Note thatif your use-case is to call a cloud function from your UI, you can <code>onCall</code> CGF. Some of the boiler plate around security is taken care for you. Let's try to add an onCall function!
<br/><br/>
<h2>Step 5: onCall function to insert in DB</h2>
<ul>
<li> In <code>function/index.js</code> add the function below:
<pre><code class="language-JavaScript">exports.insertOnCall = functions.https.onCall(async (data, context) => {
console.log(`insertOnCall::Add to database ${JSON.stringify(data)}`);
const {field1, field2} = data;
await admin.firestore().collection('items').add({field1, field2});
});
</code></pre></li>
<li>Deploy it with <code>firebase deploy --only functions</code>. This will redeploy both functions.</li>
<li> Test it in your UI code. In <a href="">tutorial 1, step5</a> we defined a Create component in <code>src/component/index.js</code>, let's revisit the <code>onSumit</code> method:
<pre><code class="language-JavaScript">onSubmit = (e) => {
e.preventDefault();
// insert by calling cloud function
const insertDB = firebase.functions().httpsCallable('insertOnCall'); // [1]
insertDB(this.state).then((result) => { // [2]
console.log(`::Result is ${JSON.stringify(result)}`);
this.setState({
field1: '',
field2: '',
});
this.props.history.push("/")
}).catch((error) => {
console.error("Error adding document: ", error);
});
};
</code></pre>
<br/>In [1] we passed the name of the function to retrieve a reference, we simply call this function in [2] with json object containing all the fields we need.
</li>
</ul>
<br/>
<h2>Where to go from here?</h2>
In this tutorial, you've get acquainted with google function in its most simple form: http triggered. To go further into learning how to code GCF, the best way is to look at existing code: the <a href="https://github.com/firebase/functions-samples">firebase/functions-samples</a> on GitHub is the perfect place to explore.
<br/>
In next tutorials we'll explore the different use-cases that fit best a cloud function.
<br/><br/>
Stay tuned!
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-77345923983152304442019-01-17T05:59:00.000-08:002019-01-17T08:31:47.819-08:00FaaS tutorial 1: Start with Firebase and prepare the groundAs being an organiser of <a href="http://rivieradev.fr/">RivieraDEV</a>, I was looking for a platform to host our CFP (call for paper). I've bumped into the open source project <a href="https://github.com/bpetetot/conference-hall">conference-hall</a> wandering on twitter (the gossip 🐦 bird <i>is useful</i> from time to time).
<br/><br/>
The app is nicely crafted and could be used free, even better I've learned afterward, there is an hosted version! That's the one I wanted to use but we were missing one key feature: sending email to inform speaker of the deliberations and provide a way for speakers to confirm their venue.
<br/><br/>
💡💡💡Open Source Project? Let's make the world 🌎 better by contributing...
<br/><br/>
On a first look, conference-hall is a web app deployed on Google Cloud Platform. The SPA is deployed using Firebase tooling and make use of firestore database.
By contributing to the project, I get acquainted to Firebase. Learning something new is cool, sharing it is even better 🤗 🤩
<br/><br/>
Time to start a series of blogs post on the <a href="https://en.wikipedia.org/wiki/Function_as_a_service">FaaS</a> subject. I'd like to explore Google functions as a service but also go broader and see how it is implemented in open source world.
<br/><br/>
In this first article, I'll share with your how to get started configuring a project from scratch in Firebase and how to deploy it to get a ground project to introduce cloud functions in the next post. Let's get started step by step...
<br/><br/>
<h2>Step 1️⃣: Initialise firebase project</h2>
Go to <a href="https://console.firebase.google.com/u/0/">Firebase console</a> and Create a firebase project, let's name it <code>test</code>
<br/><br/>
<h2>Step 2️⃣: Use Firestore</h2>
<ul>
<li>In left-hand side menu select Database tab, then click <code>Create Database</code>. Follow <a href="https://firebase.google.com/docs/firestore/quickstart?authuser=0">Firestore documentation</a> if in trouble. The Firebase console UI is quite easy to follow. Note Firestore is still beta at the time of writing.</li>
<li>Choose <code>Start in test mode</code> then click <code>Enabled</code> button.</li>
</ul>
You should be forwarded to the Database explorer, you can now add a new collection <code>items</code> as below:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1TwlwLhvDRinp8_-Sg3EYe8jf-gF2JYdlwVaI5fT4fnLd3RVdaOwgZPA3Iio9osU-NFR5ndehLd3VHeUn7UYuP0JL16yNTpoKGkz7Qojfz8nLLzlDf0kmZgqS9D-FKGkJiCbcpQkFsNM/s1600/unspecified-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1TwlwLhvDRinp8_-Sg3EYe8jf-gF2JYdlwVaI5fT4fnLd3RVdaOwgZPA3Iio9osU-NFR5ndehLd3VHeUn7UYuP0JL16yNTpoKGkz7Qojfz8nLLzlDf0kmZgqS9D-FKGkJiCbcpQkFsNM/s640/unspecified-1.png" width="640" height="566" data-original-width="868" data-original-height="768" /></a></div>
<h2>Step 3️⃣: Boostrap app</h2>
We use create-react-app to get an initial react app
<pre><code class="language-JavaScript">npx create-react-app test-crud
cd test-crud
npm install --save firebase
</code></pre>
and then we've added firebase SDK.
<br/><br/>
<h3>Insert firebase config</h3>
<ul>
<li> We use <a href="https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables">react-script env variable support</a></li>
<li> In <code>env.local</code> copy variable from firebase console
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYKQtnf2ASP1zu2ad4gFrMEs5TOl5yHe8Jqk5l78r8Av2qPDVv6ELytHACilvV5jn5k_rh_IQeNV_8f1lckOUc6KJTKdq97OMq-s3vDSriHEjYmXxaqnEG_7v46KDHOYSNZslcVwz5WLA/s1600/firebase-config.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYKQtnf2ASP1zu2ad4gFrMEs5TOl5yHe8Jqk5l78r8Av2qPDVv6ELytHACilvV5jn5k_rh_IQeNV_8f1lckOUc6KJTKdq97OMq-s3vDSriHEjYmXxaqnEG_7v46KDHOYSNZslcVwz5WLA/s640/firebase-config.png" width="640" height="442" data-original-width="1084" data-original-height="748" /></a></div></li>
<li> In <code>src/firebase/firebase.js</code>, read env variable and initialise
<pre><code class="language-JavaScript">const config = {
apiKey: process.env.REACT_APP_API_KEY,
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_DATABASE_URL,
projectId: process.env.REACT_APP_PROJECT_ID,
storageBucket: process.env.REACT_APP_STARAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
};
firebase.initializeApp(config);
firebase.firestore().settings(settings);
</code></pre></li></ul>
This way you keep your secret safe, not committed in your code 🤫🤫🤫
<br/><br/>
<h2>Step 4️⃣: Add routing</h2>
<pre><code class="language-JavaScript">npm install --save react-router-dom
mkdir src/components
touch src/components/create.js
</code></pre></li>
And define route in <code>src/index.js</code>
<pre><code class="language-JavaScript">ReactDOM.render(
<Router>
<div>
<Route exact path='/' component={App} />
<Route path='/create' component={Create} />
</div>
</Router>,
document.getElementById('root')
);
</code></pre>
In the root path, we'll display the list of items. In the the <code>Create</code> component we'll define a form component to add new items to the list.
<br/><br/>
<h2>Step 5️⃣: Access Firestore in the app</h2>
Let's define the content of <code>Create</code> component in <code>src/component/index.js</code>
<pre><code class="language-JavaScript">class Create extends Component {
constructor() {
super();
this.ref = firebase.firestore().collection('items'); // [1] retrieve items reference
this.state = {
field1: '',
field2: '',
};
}
onChange = (e) => {
const state = this.state;
state[e.target.name] = e.target.value;
this.setState(state);
};
onSubmit = (e) => {
e.preventDefault();
const { field1, field2 } = this.state;
this.ref.add({ // [2] Insert by using firestore SDK
field1,
field2,
}).then((docRef) => {
this.setState({
field1: '',
field2: '',
});
this.props.history.push("/")
}).catch((error) => {
console.error("Error adding document: ", error);
});
};
render() {
const { field1, field2 } = this.state;
return (
<div>
<div>
<div>
<h3>
Add Item
</h3>
</div>
<div>
<h4>Link to="/" >Items List</Link></h4>
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor="title">field1:</label>
<input type="text" name="field1" value={field1} onChange={this.onChange} />
</div>
<div>
<label htmlFor="title">field2:</label>
<input type="text" name="field2" value={field2} onChange={this.onChange} />
</div>
<button type="submit">Submit</button>
</form>
</div>
</div>
</div>
);
}
}
export default Create;
</code></pre>
It seems a lot of code but the key points are [1] and [2] where we use the firestore SDK to add a new item in the database directly from the client app. the call in [2] is going to be revisited in next blog post to make usage of cloud function.
<br/><br/>
<h2>Step 6️⃣: Deploy on firebase</h2>
So we build a small test app accessing firestore DB let's deploy it on the cloud with Firebase tooling 👍 !
<ul>
<li> Start running a production build
<pre><code class="language-JavaScript">$ npm run build
</code></pre></li>
<li> Install firebase tools
<pre><code class="language-JavaScript">$ npm install -g firebase-tools
$ firebase login
</code></pre></li>
<li> Init function
<pre><code class="language-JavaScript">$ firebase init
</code></pre>
<ul>
<li> Step 1: Select the Firebase features you want to use: Firestore Hosting. For now we focus only on deploying ie: hosting the app</li>
<li> Step 2: Firebase command-line interface will pull up your list of Firebase projects, where you pick firebase-crud.</li>
<li> Step 3: Keep the default for the Database Rules file name and just press enter.</li>
<li> Step 4: Pay attention to the question about public directory, which is the directory that will be deployed and served by Firebase.
In our case it is <code>build</code>, which is the folder where our production build is located. Type “build” and proceed.</li>
<li> Step 5: Firebase will ask you if you want the app to be configured as a single-page app. Say "yes".</li>
<li> Step 6: Firebase will warn us that we already have <code>build/index.html</code>. All fine!</li>
</ul></li>
<li> deploy!
<pre><code class="language-JavaScript">$ firebase deploy
...
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/test-83c1a/overview
Hosting URL: https://test-83c1a.firebaseapp.com
</code></pre></li>
</ul>
<br/><br/>
<h2>Where to go from there?</h2>
In this blog post you've seen how to configure and deploy an SPA on firebase and how to set up a Firestore DB.
Next blog post, you'll see how to write you first Google Cloud Function. Stay tuned. CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-59545047734178970592018-09-13T06:07:00.001-07:002018-09-13T23:05:00.379-07:00Unpublish a npm packageLast week, I was playing with semantic-release. Giving your CI control over your semantic release. Sweet. I should dedicate a writing on it (to come later).
<br/>
Nevertheless, I got in a situation that an erroneous version number get released (wrong commit message). Without a major version bump, a breaking change in the lib won't be reflecting (breaking the whole purpose of semantic release). 😱😱😱😱
<br/><br/>
<h2>Unpublish a "recent" version</h2>
<br/>
If you try to unpublish a version just released:
<pre><code class="language-JavaScript">$ npm publish .
+ launcher-demo@5.0.0
$ npm unpublish launcher-demo@5.0.0
- launcher-demo@5.0.0
</code></pre>
<br/>
It's ok! Pff you can do it. 😅😅😅😅
<br/>
Now is it possible later to publish the same version?
<br/>
<pre><code class="language-JavaScript">$ npm publish .
npm ERR! publish Failed PUT 400
npm ERR! code E400
npm ERR! Cannot publish over previously published version "5.0.0". : launcher-demo
</code></pre>
<br/>
It makes sense you can't use the same version, so if you update <code>package.json</code> to 5.0.1:
<pre><code class="language-JavaScript">$ npm publish .
+ launcher-demo@5.0.1
</code></pre>
<br/>
Just fine!
<br/><br/>
<h2>Unpublish a "old" version</h2>
<br/>
Let's say I want to unpublish a version released last week:
<pre><code class="language-JavaScript">$ npm unpublish launcher-demo@3.2.8
npm ERR! unpublish Failed to update data
npm ERR! code E400
npm ERR! You can no longer unpublish this version. Please deprecate it instead
</code></pre>
<br/>
Thanks <code>npm</code> for your kind suggestion, let try to deprecate it with an short message:
<pre><code class="language-JavaScript">$ npm deprecate launcher-demo@3.2.8 'erronous version'
</code></pre>
<br/>
At least now the package is visible as deprecated, trying to pull it will display a deprecate warning.
<pre><code class="language-JavaScript">$ npm i launcher-demo@3.2.8
npm WARN deprecated launcher-demo@3.2.8: erronous version
</code></pre>
<br/><br/>
<h2>Unpublish policy</h2>
<br/>
"Old", "recent" version. What does it all mean? Let's check the <a href="https://www.npmjs.com/policies/unpublish">npm unpublish policy</a>
<br/><br/>
<div style="border-style: solid;border-color:grey;background: lightgrey;padding:1em"><strong>Quote:</strong>
If the package is still within the first 72 hours, you should use one of the following from your command line:
<br/>
<ul>
<li><code>npm unpublish <package_name> -f</code> to remove the entire package thanks to the -f or force flag</li>
<li><code>npm unpublish <package_name>@<version></code> to remove a specific version</li>
</ul>
<br/>
<strong>Some considerations</strong>:
<br/>
Once package@version has been used, you can never use it again. You must publish a new version even if you unpublished the old one.<br/>
If you entirely unpublish a package, nobody else (even you) will be able to publish a package of that name for 24 hours.
<br/>
</div>
<br/>
After the <a href="https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm">one-developer-just-broke-Node</a> buzzy affair in March 2016, the unpublish policies were changed. A 10-lines library used every where should not put the whole JS community down. A step toward more immutability won't arm.
<br/><br/>
<h2>Where to go from there</h2>
<br/>
Error releasing your package?
<br/>
You've got 72 hours to fix it. 👍👍👍👍
<br/>
otherwise deprecate it.
<br/>
Maybe, it's time to automate releasing with your CI. 😇😇😇😇
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPS90OMCbuXk-tWZQD98riUDR48hrdLx3B59oSrLOvHTjIdZ7S_2t04-Q8PBq5yN6w0lYPPGXdOSdauyOy5-hXZubxANJ7PgnPcd36vhKRpdh6Tt1rs91irPyQKjDRUnahB7hfjixJ1nc/s1600/bender.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPS90OMCbuXk-tWZQD98riUDR48hrdLx3B59oSrLOvHTjIdZ7S_2t04-Q8PBq5yN6w0lYPPGXdOSdauyOy5-hXZubxANJ7PgnPcd36vhKRpdh6Tt1rs91irPyQKjDRUnahB7hfjixJ1nc/s320/bender.png" width="309" height="320" data-original-width="392" data-original-height="406" /></a></div>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-1080254687828673262017-06-25T09:43:00.001-07:002017-06-25T09:43:34.955-07:00Dirty secrets on dependency injection and Angular - part 2In the previous post <a href="http://corinnekrych.blogspot.fr/2017/06/dirty-secrets-on-dependency-injection.html">"Dirty secrets on dependency injection and Angular - part 1"</a>, you've explored how DI at component level, can produce different instances of a service. Then you've experienced DI at module level. Once a service is declared using one token in the AppModule, the same instance is shared across all the modules and components of the app.
<br/><br/>
In this article, let's revisit DI in the context of lazy-loading modules. You'll see the feature modules dynamically loaded have a different behaviour.
<br/><br/>Let's get started...
<br/><br/>
<h2>Tour of hero app</h2>
<br/>
Let's reuse the tour of heroes app that you should be familiar with from our previous post.
All source code could be find on <a href="https://github.com/corinnekrych/tour-of-heroes-webpack">github</a>.
<br/><br/>
As a reminder, in our Tour of heroes, the app displays a Dashboard page and a Heroes page. We've added a <code>RecentHeroCompoent</code> that displays the recently selected heroes in both pages. This component uses the <code>ContextService</code> to store the recently added heroes.
<br/><br/>
In the previous blog, you've worked your way to refactor the app and introduced a <code>SharedModule</code> that contains <code>RecentHeroCompoent</code> and use the <code>ContextService</code>. Let's refactor the app to break it into more feature modules:
<ul>
<li><code>DashboardModule</code> to contain the <code>HeroSearchComponent</code> and <code>HeroDetailComponent</code></li>
<li><code>HeroesModule</code> to contain the <code>HeroesComponent</code></li>
</ul>
<br/><br/>
<h2>Features module</h2>
<br/>
Here is a schema of what you have in the <a href="https://github.com/corinnekrych/tour-of-heroes-webpack/tree/lazy.load.routing.shared">lazy.loading.routing.shared github branch</a>:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5t6rskSpNygBe8gRdZlYMEdj-95RoTc4XucJpVMvj0lR2HqYN8-S4pOvAtmgvJcW2Ms-IM4Svl02Mari4TFQJfpv7kwdP-zERSw1ugQkpe8AMRxPlIYaHUewwFJIr3QM2v650C6Odkn4/s1600/Screenshot+2017-06-25+18.26.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5t6rskSpNygBe8gRdZlYMEdj-95RoTc4XucJpVMvj0lR2HqYN8-S4pOvAtmgvJcW2Ms-IM4Svl02Mari4TFQJfpv7kwdP-zERSw1ugQkpe8AMRxPlIYaHUewwFJIr3QM2v650C6Odkn4/s640/Screenshot+2017-06-25+18.26.46.png" width="640" height="453" data-original-width="1600" data-original-height="1133" /></a></div>
<br/>
<code>DashboardModule</code> is as below:
<br/>
<pre><code class="language-JavaScript">@NgModule({
imports: [
CommonModule,
FormsModule,
DashboardRoutingModule, // [1]
HeroDetailModule,
SharedModule // [2]
],
declarations: [
DashboardComponent,
HeroSearchComponent
],
exports: [],
providers: [
HeroService,
HeroSearchService
]
})
export class DashboardModule { }
</code></pre>
<br/>
In [1] you define <code><a href="https://github.com/corinnekrych/tour-of-heroes-webpack/blob/lazy.load.routing.shared/src/app/dashboard/dashboard-routing.module.ts">DashboardRoutingModule</a></code>.
<br/><br/>
In [2] you import <code>SharedModule</code> which defines common components like <code>SpinnerComponent</code>, <code>RecentHeroesComponent</code>.
<br/><br/>
<code>HeroModule</code> is as below:
<br/>
<pre><code class="language-JavaScript">@NgModule({
imports: [
CommonModule,
FormsModule,
HeroDetailModule,
SharedModule, // [1]
HeroesRoutingModule
],
declarations: [ HeroesComponent ],
exports: [
HeroesComponent,
HeroDetailComponent
],
providers: [ HeroService ] // [2]
})
export class HeroesModule { }
</code></pre>
<br/>
In [1] you import <code>SharedModule</code> which defines common components like <code>SpinnerComponent</code>, <code>RecentHeroesComponent</code>.
<br/>
Note in [2] that <code>HeroService</code> is defined as provider in both modules. It could be a candidate to be provided by SharedModule. This service is stateless however. Having multiple instances won't bother us as much as a stateful service.
<br/><br/>
Last, let's look at <code>AppModule</code>:
<br/>
<pre><code class="language-JavaScript">@NgModule({
declarations: [ AppComponent ], // [1]
imports: [
BrowserModule,
FormsModule,
HttpModule,
SharedModule, // [2]
InMemoryWebApiModule.forRoot(InMemoryDataService),
AppRoutingModule // [3]
],
providers: [], // [4]
bootstrap: [ AppComponent ],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
</code></pre>
<br/>
In [1], the <code>declarations</code> section is really lean as most components are declared either in the features module or in the shared module.
<br/><br/>
In [2], you now import the <code>SharedModule</code> form AppModule. <code>SharedModule</code> is also imported in the feature modules. From our previous post we know, in statically loaded module the last declared token for a shared service wins. There is eventually only one instance defined. Is it the same for lazy-loading?
<br/><br/>
In [3] we defined the module for lazy loading, more in next section.
<br/><br/>
In [4], <code>providers</code> section is lean similar to <code>declarations</code> as most providers are defined at module level.
<br/><br/>
<h2>Lazy loading modules</h2>
<br/>
<code>AppRoutingModule</code> is as below:
<br/>
<pre><code class="language-JavaScript">const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardModule' }, // [1]
{ path: 'detail/:id', loadChildren: './dashboard/dashboard.module#DashboardModule' },
{ path: 'heroes', loadChildren: './heroes/heroes.module#HeroesModule' }
]
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
</code></pre>
<br/>
In [1], you'll define lazy load <code>DashboardModule</code> with <code>loadChildren</code> routing mechanism.
<br/><br/>Running the app, you can observe the same syndrom as when we define <code>ContextService</code> at component level: <code>DashboardModule</code> has a different instance of <code>ContextService</code> than <code>HeroesModule</code>. This is easily observable with 2 different lists of recently added heroes.
<br/><br/>
Checking <a href="https://angular.io/guide/ngmodule-faq#why-does-lazy-loading-create-a-child-injector">angular.io module FAQ</a>, you can get an explanation for that behaviour:
<br/><br/>
<quote>
Angular adds <code>@NgModule.providers</code> to the application root injector, unless the module is lazy loaded. For a lazy-loaded module, Angular creates a child injector and adds the module's providers to the child injector.
<br/><br/>
Why doesn't Angular add lazy-loaded providers to the app root injector as it does for eagerly loaded modules?
<br/>
The answer is grounded in a fundamental characteristic of the Angular dependency-injection system. An injector can add providers until it's first used. Once an injector starts creating and delivering services, its provider list is frozen; no new providers are allowed.
</quote>
<br/><br/>What about if you what a singleton shared across all your app for <code>ContextService</code>? There is a way...
<br/><br/>
<h2>Recycle provider with forRoot</h2>
<br/>
Similar to what <code>RouterModule</code> uses: forRoot.
Here is a schema of what you have in the <a href="https://github.com/corinnekrych/tour-of-heroes-webpack/tree/lazy.load.routing.forRoot">lazy.loading.routing.forRoot github branch</a>:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr-6ZS5XOMsOKGRYay44z8zqqPQZ64eUleeagvRK7Pdh6cUyU321rqrUmQSgBoUcEd0DGuakeT1aaZV96eF8Q0DTDYcYT16C2u4mmURgt7bRuE_ijFZA5ENTrfbXv1onuxvGFRKCKtITM/s1600/Screenshot+2017-06-25+18.28.12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr-6ZS5XOMsOKGRYay44z8zqqPQZ64eUleeagvRK7Pdh6cUyU321rqrUmQSgBoUcEd0DGuakeT1aaZV96eF8Q0DTDYcYT16C2u4mmURgt7bRuE_ijFZA5ENTrfbXv1onuxvGFRKCKtITM/s640/Screenshot+2017-06-25+18.28.12.png" width="640" height="452" data-original-width="1600" data-original-height="1130" /></a></div>
<br/><br/>
In <code>SharedModule</code>:
<br/>
<pre><code class="language-JavaScript">@NgModule({
imports: [
CommonModule
],
declarations: [
SpinnerComponent,
RecentHeroComponent
],
exports: [
SpinnerComponent,
RecentHeroComponent
],
//providers: [ContextService], // [1]
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
})
export class SharedModule {
static forRoot() { // [2]
return {
ngModule: SharedModule,
providers: [ ContextService ]
}
}
}
</code></pre>
<br/>
In [1] remove <code>ContextService</code> as a providers. Define in [2] a <code>forRoot</code> method (the naming is an broadly accepted convention) that
returns a <a href="https://angular.io/api/core/ModuleWithProviders">ModuleWithProviders</a> interface. This interface define a Module with a given list of providers. <code>SharedModule</code> will reuse defined <code>ContextService</code> provider defined at <code>AppModule</code> level.
<br/><br/>
In all feature modules, imports <code>SharedModule</code>.
<br/><br/>
In <code>AppModule</code>:
<br/>
<pre><code class="language-JavaScript">@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
//SharedModule, // [1]
SharedModule.forRoot(), // [2]
InMemoryWebApiModule.forRoot(InMemoryDataService),
AppRoutingModule
],
providers: [],
bootstrap: [
AppComponent
],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {
}
</code></pre>
<br/>
In [1] and [2], replace the <code>SharedModule</code> imports by <code>SharedModule.forRoot()</code>. You should only call forRoot at highest level ie: <code>AppModule</code> level otherwise you will run in multiple instances.
<br/><br/>To see the source code, take a look at <a href="https://github.com/corinnekrych/tour-of-heroes-webpack/tree/lazy.load.routing.forRoot">lazy.loading.routing.forRoot github branch</a>:
<br/><br/>
<h2>Where to go from there</h2>
<br/>
In this blog post you've seen how <code>providers</code> on lazy-loaded modules behaves differently that in an app with eagerly loaded modules.
<br/><br/>Dynamic routing brings its lot of complexity and can introduce difficult-to-track bugs in your app. Specially if you refactor from statically loaded modules to lazy loaded ones. Watch out your shared module specially if they provide services.
<br/><br/>The Angular team even recommends to avoid providing services in shared modules. If you go that route, you still have the forRoot alternative.
<br/><br/>Happy coding!CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-22440880767684898692017-06-16T02:45:00.001-07:002017-07-05T22:56:17.899-07:00Dirty secrets on dependency injection and Angular - part 1Let's talk about Dependency Injection (DI) in Angular. I'd like to take a different approach and tell you the stuff that surprise me when I've first learned them using Angular on larger apps...
<br><br/>Key feature from Angular even since AngularJS (ie: Angular 1.X), DI is a pure treasure from Angular, but injector hierarchy can be difficult to grasp at first. Add routing and dynamic load of modules and all could go wild... Services get created multiple times and if stateful (yes functional lovers, you sometimes need states) the global states (even worse 😅) is out of sync in some parts of your app.
<br/>To get back in control of the singleton instances created for your app singleton, you need to be aware of a few things.
<br/><br/>Let's get started...
<br/><br/>
<h2>Tour of hero app</h2>
<br/>
Let's reuse the tour of heroes app that you should be familiar with from when you first started at angular.io.
Thansk to <a href="https://github.com/LarsKumbier">LarsKumbier</a> for adapting it to webpack, I've forked the repo and adjust it to my demo's needs. All source code could be find on <a href="https://github.com/corinnekrych/tour-of-heroes-webpack">github</a>.
<br/><br/>
In this version of Tour of heroes, the app displays a Dashboard page and a Heroes page. I've added a <code>RecentHeroCompoent</code> that displays the recently selected heroes in both pages. This component uses the <code>ContextService</code> to store the recently added heroes.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3qQ1VO4-eeCvU6HhM3Pt7g-KMwDwnzLQwTbelNttSV6-jlEfzLL8Te8o8mQDv6IBSEtBHkizMWeXRozlKSkc0ltvqZ-L3IH0I63JZJE1szqmaP8aQnVo1IELzV0zY0TKXyAeiJafqq_w/s1600/Screenshot+2017-06-16+11.06.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3qQ1VO4-eeCvU6HhM3Pt7g-KMwDwnzLQwTbelNttSV6-jlEfzLL8Te8o8mQDv6IBSEtBHkizMWeXRozlKSkc0ltvqZ-L3IH0I63JZJE1szqmaP8aQnVo1IELzV0zY0TKXyAeiJafqq_w/s640/Screenshot+2017-06-16+11.06.54.png" width="640" height="449" data-original-width="960" data-original-height="674" /></a></div>
<br/>
See AppModule in master branch.
<br/><br/>
<h2>Provider at Component level</h2>
<br/>
Let's go to <code>HeroSearchComponent</code> in <code>src/app/hero-search/hero-search.component.ts</code> file and change the <code>@Component</code> decorator:
<br/>
<pre><code class="language-JavaScript">@Component({
selector: 'hero-search',
templateUrl: './hero-search.component.html',
styleUrls: ['./hero-search.component.css'],
providers: [ContextService] // [1]
})
export class HeroSearchComponent implements OnInit {
</code></pre>
<br/>
if you add line [1], you get something like this drawing:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGedUJ8CQ2ySuz0Ju_qqFT5bk1dHoNW4faXzB4jafaXg_QOQUYxSHh-U49oDjK1x78W01qqxs7QxJyT1oHiF4AnJN9OMHVP1D4rX7SpWH1cc8g4IT9a2c3tT2a3XbfLAO1q46IdmESTxk/s1600/Screenshot+2017-06-16+11.07.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGedUJ8CQ2ySuz0Ju_qqFT5bk1dHoNW4faXzB4jafaXg_QOQUYxSHh-U49oDjK1x78W01qqxs7QxJyT1oHiF4AnJN9OMHVP1D4rX7SpWH1cc8g4IT9a2c3tT2a3XbfLAO1q46IdmESTxk/s640/Screenshot+2017-06-16+11.07.09.png" width="640" height="449" data-original-width="957" data-original-height="671" /></a></div>
<br/><br/>Run the app again.
<br/>What do you observe?
<br/>The heroes page is working fine listing below the recently visited heroes. However going to Dashboard/SearchHeroComponent, the recently visited heroes list is empty!!
<br/><br/>
The recently added heroes is empty in <code>HeroSeachComponent</code> because you've got a different instance of <code>ServiceContext</code>. Dependency injection in Angular relies on hierarchical injectors that are linked to the tree of components.
This means that you can configure providers at different levels:
<ul>
<li>for the whole application when bootstrapping it in the <code>AppModule</code>. All services defined in providers will share the same instance.</li>
<li>for a specific component and its sub components. Same as before but for à specific component. so if you redefine providers at Component level, you got a different instance. You've overriden global AppModule providers.</li>
</ul>
<br/>
<div style="border-style: solid;border-color:grey;background: lightgrey;padding:1em"><strong>Tip:</strong> don't have app-scoped services defined at component level. Very rare use-cases where you actually want</div>
<br/><br/>
<h2>Provider at Module level</h2>
<br/>
What about providers at module level, if we do something like:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOuYoXOe1AKaIrevSMOOXJVnuXN9diBThct4sKtCYOO3FSY6u9rsWMrt7EKx_n8VoRC17PoR5OKgO59WzF6UZmlkqgtry3uQToeFpMAPYO-6wAliRhXRtvupIAiyJybF3yWN1Cy40vDl0/s1600/Screenshot+2017-06-16+11.10.32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOuYoXOe1AKaIrevSMOOXJVnuXN9diBThct4sKtCYOO3FSY6u9rsWMrt7EKx_n8VoRC17PoR5OKgO59WzF6UZmlkqgtry3uQToeFpMAPYO-6wAliRhXRtvupIAiyJybF3yWN1Cy40vDl0/s640/Screenshot+2017-06-16+11.10.32.png" width="640" height="449" data-original-width="958" data-original-height="672" /></a></div>
<br/><br/>
Let's first refactor the code, to introduce a <code>SharedModule</code> as defined in <a href="https://angular.io/guide/ngmodule#shared-modules">angular.io guide</a>.
In your <code>SharedModule</code>, we put the <code>SpinnerComponent</code>, the <code>RecentHeroComponent</code> and the <code>ContextService</code>. Creating the <code>SharedModule</code>, you can clean up the <code>imports</code> for <code>AppModule</code> which now looks like:
<br/><br/>
<pre><code class="language-JavaScript">@NgModule({
declarations: [
AppComponent,
HeroDetailComponent,
HeroesComponent,
DashboardComponent,
HeroSearchComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
SharedModule,
InMemoryWebApiModule.forRoot(InMemoryDataService),
AppRoutingModule
],
providers: [
HeroSearchService,
HeroService,
ContextService
],
bootstrap: [
AppComponent
]
})
export class AppModule {}
</code></pre>
<br/>
Full source code in <a href="https://github.com/corinnekrych/tour-of-heroes-webpack/blob/shared.module/src/app/app.module.ts#L29">github here</a>.
Notice <code>RecentHeroComponent</code> and <code>SpinnerComponent</code> has been removed from declarations. Intentionally the <code>ContextService</code> appears twice at <code>SharedModule</code> and <code>AppModule</code> level. Are we going to have duplicate instances?
<br/><br/>
Nope.
<br/>
A Module does not have a specific injector (as opposed to Component which gets their own injector). Therefore when <code>AppModule</code> provides a service for token <code>ContextService</code> and imports a <code>SharedModule</code> that also provides a service for token <code>ContextService</code>, then <code>AppModule</code>'s service definition "wins". This is clearly stated in <a href="https://angular.io/guide/ngmodule-faq#what-if-two-modules-provide-the-same-service">AppModule angular.io FAQ.</a>
<br/><br/>
<h2>Where to go from there</h2>
<br/>
In this blog post you've seen how <code>providers</code> on component plays an important role on how singleton get created. Modules are a different story, they do not provide encapsulation as component.
<br/>
Next blog posts, you will see how DI and dynamically loaded modules plays together. Stay tuned.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-65215064415445971382017-05-30T00:35:00.000-07:002017-06-12T03:04:35.487-07:00Going Headless without headacheYou're done with a first beta of your angular 4 app.<br/>
Thanks to <a href="http://corinnekrych.blogspot.fr/2017/05/testing-your-services-with-angular.html">Test Your Angular Services</a> and <a href="http://corinnekrych.blogspot.fr/2017/05/test-your-angular-component.html">Test your Angular component</a>, you get a good test suite 🤗. It runs ok with a <code>npm test</code> on your local dev environment. Now, is time to automate it and have it run against a CI server: be Travis, Jenkins, choose your weapon. But most probably you will need to run you test headlessly.
<br/><br/>
Until recently the only way to go is to use PhantomJS, a "headless" browser that can be run via a command line and is primarily used to test websites without the need to completely render a page.
<br/><br/>
Since Chrome 59 (still in beta), you can now use Chrome headless! In this post we'll see how to go headless: the classical way with PhamtomJS and then we'll peek a boo into Chrome Headless. You may want to wait for official release of 59 (it should be expected to roll out very soon in May/June this year).
<br/><br/>
<h3>Getting started with angular-cli</h3>
<br/>
Let's use <a href="https://github.com/angular/angular-cli">angular-cli</a> latest release (v1.0.6 at the time of writing), make sure you have install it in [1].
<br/>
<pre><code class="language-JavaScript">npm install -g @angular/cli // [1]
ng new MyHeadlessProject // [2]
cd MyHeadlessProject
npm test // [3]
</code></pre>
In [2], create a new project, let's call it <code>MyHeadlessProject</code>.
<br/>
In [3], run your test. You can see by default the test run in watch mode.
If you explore <code>karma.conf.js</code>:
<pre><code class="language-JavaScript">module.exports = function (config) {
config.set({
...
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'], // [1]
singleRun: false // [2]
});
</code></pre>
If you switch [2] to false, you can go for a single run.
<br/>
To be headless you would have had to change Chrome for PhantomJS.
<br/><br/>
<h3>Go headless with PhamtomJS</h3>
<br/>
First, install the phantomjs browser and its karma launcher with:
<pre><code class="language-JavaScript">npm i phantomjs karma-phantomjs-launcher --save-dev
</code></pre>
Next step is to change the the karma configuration:
<br/>
<pre><code class="language-JavaScript">browsers: ['PhantomJS', 'PhantomJS_custom'],
customLaunchers: {
'PhantomJS_custom': {
base: 'PhantomJS',
options: {
windowName: 'my-window',
settings: {
webSecurityEnabled: false
},
},
flags: ['--load-images=true'],
debug: true
}
},
phantomjsLauncher: {
exitOnResourceError: true
},
singleRun: true
</code></pre>
and don't forgot to import them at the beginning of the file:
<br/>
<pre><code class="language-JavaScript">plugins: [
require('karma-jasmine'),
require('karma-phantomjs-launcher'),
],
</code></pre>
Running it you got the error:
<br/>
<pre><code class="language-JavaScript">PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
TypeError: undefined is not an object (evaluating '((Object)).assign.apply')
at webpack:///~/@angular/common/@angular/common.es5.js:3091:0 <- src/test.ts:23952
</code></pre>
As per this <a href="https://github.com/angular/angular-cli/issues/4654">angular-cli issue</a>, go to <code>polyfills.js</code> and uncomment
<br/>
<pre><code class="language-JavaScript">import 'core-js/es6/object';
import 'core-js/es6/array';
</code></pre>
Rerun, tada !
<br/>
It works!
<br/>... Until you run into a next polyfill error. PhantomJS is not the latest, even worse, it's getting deprecated.
Even PhantomJS main maintainer <a href="https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE">Vitali is stepping down as a maintainer</a> recommending to switch to chrome headless.
It's always cumbersome to have a polyfilll need just for your automated test suite, let's peek a boo into Headless Chrome.
<br/><br/>
<h3>Chrome headless</h3>
<br/>
First of all, you either need to have Chrome beta installed or have ChromeCanary.
<br/>On Mac:
<pre><code class="language-JavaScript">brew cask install google-chrome-canary
</code></pre>
Next step is to change the the karma configuration:
<br/>
<pre><code class="language-JavaScript">browsers: ['ChromeNoSandboxHeadless'],
customLaunchers: {
ChromeNoSandboxHeadless: {
base: 'ChromeCanary',
flags: [
'--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'--headless',
'--disable-gpu',
// Without a remote debugging port, Google Chrome exits immediately.
' --remote-debugging-port=9222',
],
},
},
</code></pre>
and don't forgot to import them at the beginning of the file:
<br/>
<pre><code class="language-JavaScript">plugins: [
...
require('karma-chrome-launcher'),
],
</code></pre>
Rerun, tada!
No need to have any polyfill.
<br/><br/>
<h3>What's next?</h3>
<br/>
In this post you saw how to run your test suite headlessly to fit your test automation CI needs. You can get the full source code in github for <a href="https://github.com/corinnekrych/MyHeadlessProject/tree/step1">PhantomJs in this branch</a>, and for <a href="https://github.com/corinnekrych/MyHeadlessProject/tree/step2">Chrome Headless with Canary in this branch</a>.
Have fun and try it on your project!
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-29058866185216818172017-05-19T02:03:00.000-07:002017-05-19T02:04:28.718-07:00Test your Angular componentIn my previous post <a href="http://corinnekrych.blogspot.fr/2017/05/testing-your-services-with-angular.html">"Testing your Services with Angular"</a>, we saw how to unit test your Services using DI (Dependency Injection) to inject mock classes into the test module (TestBed). Let's go one step further and see how to unit test your components.
<br/><br/>
With component testing, you can:
<ul>
<li>either test at unit test level ie: testing all public methods. You merely test your javascript component, mocking service and rendering layers.</li>
<li>or test at component level, ie: testing the component and its template together and interacting with Html element.</li>
</ul>
I tend to use both methods whenever it makes the more sense: if my component has large template, do more component testing.
<br/><br/>
Another complexity introduced by component testing is that most of the time you have to deal with the async nature of html rendering. But let's dig in...
<br/><br/>
<h3>Setting up tests</h3>
<br/>
I'll use the code base of openshift.io to illustrate this post. It's a big enough project to go beyond the getting started apps. Code source could be found in:
<a href="https://github.com/fabric8io/fabric8-ui/">https://github.com/fabric8io/fabric8-ui/</a>. To run the test, use <code>npm run test:unit</code>.
<br/><br/>
<h3>Component test: DI, Mock and shallow rendering</h3>
<br/>
In the previous article <a href="http://corinnekrych.blogspot.fr/2017/05/testing-your-services-with-angular.html">"Testing your Services with Angular"</a>, you saw how to mock service through the use of DI. Same story here, in <code>TestBed.configureTestingModule</code> you define your testing NgModule with the providers. The providers injected at NgModule are available to the components of this module.
<br/><br/>
For example, let's add a component test for <code>CodebasesAddComponent</code> a wizard style component to add a github repository in the list of available codebases. First, you enter the repository name and hit "sync" button to check (via github API) if the repo exists. Upon success, some repo details are displayed and a final "associate" button add the repo to the list of codebases.
<br/><br/>
To test it, let's create the TestBed module, we need to inject all the dependencies in the <code>providers</code>. Check the <a href="https://github.com/corinnekrych/fabric8-ui/blob/component.unit.tests/src/app/create/codebases/codebases-add/codebases-add.component.ts#L40-L47">constructor of the <code>CodebasesAddComponent</code></a>, there are 7 dependencies injected!
<br/><br/>
Let's write <code>TestBed.configureTestingModule</code> and inject 7 fake services:
<br/>
<pre><code class="language-JavaScript">beforeEach(() => {
broadcasterMock = jasmine.createSpyObj('Broadcaster', ['broadcast']);
codebasesServiceMock = jasmine.createSpyObj('CodebasesService', ['getCodebases', 'addCodebase']);
authServiceMock = jasmine.createSpy('AuthenticationService');
contextsMock = jasmine.createSpy('Contexts');
gitHubServiceMock = jasmine.createSpyObj('GitHubService', ['getRepoDetailsByFullName', 'getRepoLicenseByUrl']);
notificationMock = jasmine.createSpyObj('Notifications', ['message']);
routerMock = jasmine.createSpy('Router');
routeMock = jasmine.createSpy('ActivatedRoute');
userServiceMock = jasmine.createSpy('UserService');
TestBed.configureTestingModule({
imports: [FormsModule, HttpModule],
declarations: [CodebasesAddComponent], // [1]
providers: [
{
provide: Broadcaster, useValue: broadcasterMock // [2]
},
{
provide: CodebasesService, useValue: codebasesServiceMock
},
{
provide: Contexts, useClass: ContextsMock // [3]
},
{
provide: GitHubService, useValue: gitHubServiceMock
},
{
provide: Notifications, useValue: notificationMock
},
{
provide: Router, useValue: routerMock
},
{
provide: ActivatedRoute, useValue: routeMock
}
],
// Tells the compiler not to error on unknown elements and attributes
schemas: [NO_ERRORS_SCHEMA] // [4]
});
fixture = TestBed.createComponent(CodebasesAddComponent);
});
</code></pre>
<br/>
In line [2], you use <code>useValue</code> to inject a value (created via dynamic mock with jasmine) or use a had crafted class in [3] to mock your data. Whatever is convenient!
<br/><br/>
In line [4], you use <code>NO_ERRORS_SCHEMA</code> and in line [1] we declare only one component. This is where shallow rendering comes in. You've stubbed services (quite straightforward thanks to Dependency Injection in Angular). Now is the time to stub child components.
<br/><br/>
<b>Shallow</b> testing your component means you test your UI component in isolation. Your browser will display only the DOM part that directly belongs to the component under test. For example, if we look at the template we have another component element like <code>alm-slide-out-panel</code>. Since you declare in [1] only your component under test, Angular will give you error for unknown DOM element: therefore tell the framework it can just ignore those with <code>NO_ERRORS_SCHEMA</code>.
<br/><br/>
<b>Note:</b> To compile or not to compile TestComponent?
In most Angular tutorials, you will see the <code>Testbed.compileComponents</code>, but as specified in the <a href="https://angular.io/docs/ts/latest/guide/testing.html#!#compile-components">docs</a> this is not needed when you're using webpack.
<br/><br/>
<h3>Async testing with <code>async</code> and <code>whenStable</code></h3>
<br/>
Let's write your first test to validate the first part of the wizard creation, click on "sync" button, display second part of the wizard. See full code in <a href="https://github.com/corinnekrych/fabric8-ui/blob/refactor.test/src/app/create/codebases/codebases-add/codebases-add.component.spec.ts#L105-L127">here</a>.
<br/>
<pre><code class="language-JavaScript">fit('Display github repo details after sync button pressed', async(() => { // [1]
// given
gitHubServiceMock.getRepoDetailsByFullName.and.returnValue(Observable.of(expectedGitHubRepoDetails));
gitHubServiceMock.getRepoLicenseByUrl.and.returnValue(Observable.of(expectedGitHubRepoLicense)); // [2]
const debug = fixture.debugElement;
const inputSpace = debug.query(By.css('#spacePath'));
const inputGitHubRepo = debug.query(By.css('#gitHubRepo')); // [3]
const syncButton = debug.query(By.css('#syncButton'));
const form = debug.query(By.css('form'));
fixture.detectChanges(); // [4]
fixture.whenStable().then(() => { // [5]
// when github repos added and sync button clicked
inputGitHubRepo.nativeElement.value = 'TestSpace/toto';
inputGitHubRepo.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); // [6]
}).then(() => {
syncButton.nativeElement.click();
fixture.detectChanges(); // [7]
}).then(() => {
expect(form.nativeElement.querySelector('#created').value).toBeTruthy(); // [8]
expect(form.nativeElement.querySelector('#license').value).toEqual('Apache License 2.0');
});
}));
</code></pre>
<br/>
In [1] you see the <code>it</code> from jasmine BDD has been prefixed with a <b>f</b> to focus on this test (good tip to only run the test you're working on).
<br/><br/>
In [2] you set the expected result for stubbed service call. Notice the service return an Observable, we use <code>Observable.of</code> to wrap a result into an Observable stream and start it.
<br/><br/>
In [3], you get the DOM element, but not quite. Actually since you use debugElement you get a helper node, you can always call <code>nativeElement</code> on it to get real DOM object. As a reminder:
<br/>
<pre><code class="language-JavaScript">abstract class ComponentFixture {
debugElement; // test helper
componentInstance; // access properties and methods
nativeElement; // access DOM
detectChanges(); // trigger component change detection
}
</code></pre>
<br/>
In [4] and [5], you trigger an event for the component to be initialized. As the the HTML rendering is asynchronous per nature, you need to write asynchronous test. In Jasmine, you used to write async test using <code>done()</code> callback that need to be called once you've done with async call. With angular framework you can wrap you test inside an <code>async</code>.
<br/><br/>
In [6] you notify the component a change happened: user entered a repo name, some validation is going on in the component. Once the validation is successful, you trigger another event and notify the component a change happened in [7]. Because the flow is synchronous you need to chain your promises.
<br/><br/>
Eventually in [8] following given-when-then approach of testing you can verify your expectation.
<br/><br/>
<h3>Async testing with <code>fakeAsync</code> and <code>tick</code></h3>
<br/>
Replace <code>async</code> by <code>fakeAsync</code> and <code>whenStable / then</code> by <code>tick</code> and voilà! In here no promises in sight, plain synchronous style.
<br/>
<pre><code class="language-JavaScript">fit('Display github repo details after sync button pressed', fakeAsync(() => {
gitHubServiceMock.getRepoDetailsByFullName.and.returnValue(Observable.of(expectedGitHubRepoDetails));
gitHubServiceMock.getRepoLicenseByUrl.and.returnValue(Observable.of(expectedGitHubRepoLicense));
const debug = fixture.debugElement;
const inputGitHubRepo = debug.query(By.css('#gitHubRepo'));
const syncButton = debug.query(By.css('#syncButton'));
const form = debug.query(By.css('form'));
fixture.detectChanges();
tick();
inputGitHubRepo.nativeElement.value = 'TestSpace/toto';
inputGitHubRepo.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
tick();
syncButton.nativeElement.click();
fixture.detectChanges();
tick();
expect(form.nativeElement.querySelector('#created').value).toBeTruthy();
expect(form.nativeElement.querySelector('#license').value).toEqual('Apache License 2.0');
}));
</code></pre>
<br/><br/>
<h3>When DI get tricky</h3>
<br/>
While writing those tests, I hit the issue of a component defining its own <code>providers</code>. When your component define its own providers it means it get its own injector ie: a new instance of the service is created at your component level. Is it really what is expected? In my case this was an error in the code. Get more details on how dependency injection in hierarchy of component work read this <a href="https://blog.thoughtram.io/angular/2016/09/14/bypassing-providers-in-angular-2.html">great article</a>.
<br/><br/>
<h1>What's next?</h1>
<br/>
In this post you saw how to test a angular component in isolation, how to test asynchronously and delve a bit in DI. You can get the <a href="https://github.com/corinnekrych/fabric8-ui/blob/refactor.test/src/app/create/codebases/codebases-add/codebases-add.component.spec.ts#L105-L127">full source code in github</a>.<br/>
Next post, I'll like to test about testing headlessly for your CI/CD integration. Stay tuned.
Happy testing!
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-15899561006130302482017-05-14T08:30:00.001-07:002017-05-14T08:30:34.952-07:00RivieraDEV 2017: What a great edition!<a href="http://rivieradev.fr/">RivieraDEV</a> is over. 2 days of conferences, workshop and fun.<br/>
This 2017 edition was placed under the sign of...<br/>
Cloud.<br/> <br/>
First, cloudy weather for the speaker diner on Wednesday...
<br/>
<blockquote class="twitter-tweet" data-lang="fr"><p lang="sv" dir="ltr">Speaker dinner at <a href="https://twitter.com/RivieraDEV">@RivieraDEV</a> !! <a href="https://t.co/fOAF4kebaS">pic.twitter.com/fOAF4kebaS</a></p>— Sébastien Blanc 🇪🇺 (@sebi2706) <a href="https://twitter.com/sebi2706/status/862378988286693377">10 mai 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
Second, lots of talks about the Cloud with OpenShift, Kubernetes, Docker in prod, Cloud Foundry and even some clusters battle with chaos monkey. 🐵🙈🙉🙊
<br/><br/>
The first day started with the keynote from the RivieraDEV team where Sebastien with a virtual Mark Little, announced the first joined edition with JUDCon. To follow, Nadir Kadem's
presentation on hacking culture and Hendrik Ebbers beautifully crafted slides on a project life gives the tone of RivieraDEV: a conference for developers.
<blockquote class="twitter-tweet" data-lang="fr"><p lang="en" dir="ltr">Kick start of <a href="https://twitter.com/RivieraDEV">@RivieraDEV</a> 2017 <a href="https://t.co/UXBH6rUbrq">pic.twitter.com/UXBH6rUbrq</a></p>— corinne (@corinnekrych) <a href="https://twitter.com/corinnekrych/status/862566548669706240">11 mai 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
With 3 tracks, you have to make a call, here are the presentations I've picked.
<ul>
<li>Edson Yanaga's session on Kubernetes and OpenShift. The presentation starts form Forbes' quote: "Now every company is a software company" and focus on how to organize your team using the right tools to deliver the best business value to end users. A/B testing, features toggle, monitoring is made easy with OpenShift with a zero downtime deployment.</li>
<li>Nodejs on OpenShift by Lance Ball who shows case how to do a source to image on OpenShift console and get the latest nodejs version available. the presentation also illustrates with a short demo on circuit breaker for your micro services in nodejs.</li>
<li>Lunch break with <b>socca</b>: if you don't know what it is, you ought to come to 2018 edition and taste it!</li>
<li>Stephanie Moallic talks about how to control a robot from a hand crafted ionic2 mobile app. From under 100 euros you can get you arduino-based robot! The only limit you have is your imagination.</li>
<li>Francois Teychene tells us his experience on running Docker on production clusters. So, with docker you can't get rid of your ops department?</li>
<li>Jean-françois Garreau demos the new physical web API. Lots of new cool stuff to try out to enhance your web site UX with some vibration, notification...</li>
<li>Last presentation is a BOF session on Monade where Philippe, Nicolas, Guillaume and Gauthier illustrate functions like map, switchMap with bottles, pears and alcohol. If you don't get fluent on functional programming, I'll put it on the drinks.</li>
</ul>
<br/>
For the second day, Matthias kills the rumour on the French Riviera weather:
<blockquote class="twitter-tweet" data-lang="fr"><p lang="en" dir="ltr">nice breakfast view / <a href="https://twitter.com/gunnarmorling">@gunnarmorling</a> <a href="https://twitter.com/hashtag/RivieraDev?src=hash">#RivieraDev</a> <a href="https://t.co/LYRgMXsgTB">pic.twitter.com/LYRgMXsgTB</a></p>— El Matulã ✌️ (@mwessendorf) <a href="https://twitter.com/mwessendorf/status/862931721397862400">12 mai 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
The keynotes starts on accessibility and quality by Aurelien Levy, a subject very often overlooked. You, as a developer have the power to change other person's life. To carry on "with great powers come great responsibilities", Guillaume Champeau talks about ethics in IT and the privacy concern when googling.
<br/>
Again, with 3 tracks, here are the presentations I've picked.
<ul>
<li>Julien Viet talsk about Http2 multiplexing theory. With a very visual demo of http1 vs http2 verticles loading in high latency images. Here is the link: <a href="https://github.com/aesteve/http2-showcase">http2-showcase</a>! Also I get out of the presentation, with the willing to dig a bit more GRPC with protocol buffer to even encode better and reduce payload.</li>
<li>To follow, Thomas presents Reactive programming with Eclipse Vert.X. With a live demo, he shows a verticle with reactive wrapper. I've learned about Single RxJava class, a special case of single-event Observable. I need to dig in <a href="https://github.com/tsegismont/vertx-musicstore">Thomas' music store demo</a>.</li>
<li>Back for some docker, with skynet vs monkey planet fight. I really enjoy the light tone of the presentation.</li>
<li>Josh Long demos how to deploy on Cloud Foundry using AB testing, zuul configuration... Nice demo, I'm even on the demo. Thanks my friend 😊</li>
<li>Some CSS novelties with GridLayout, I'm not yet ready for it, still learning. Thanks Raphael for the nice intro, quite in-depth some time, I'll need to review some slides.</li>
</ul>
<br/>
So this is the end, time to say good-bye my friends. Thanks to all our speakers to join us to make a great edition. Last but not least, thanks to all the attendees for coming and make the edition so special: best attendee record this year, over 400!<br/>
See you all for 2018 edition.
<br/>
<br/>PS: If you want to read more blog post on RivieraDEV, I recommend <a href="https://hikinginsoftwareland.wordpress.com/2017/05/13/rivieradev-2017/">Fanny's post</a>.
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-85956427351221856492017-05-09T07:11:00.000-07:002017-05-19T02:04:39.935-07:00Testing your Services with AngularHave you ever joined a project to find out it is missing unit tests?<br/>
So as the enthusiastic new comer, you've decided to roll your sleeves 💪 and you're up to add more unit tests. In this article, I'd like to share the fun of seeing the code coverage percentage increased 📊 📈 in your angular application.
<br/><br/>
I love <a href="https://angular.io">angular.io documentation</a>. Really great content and since it is part of angular repo it's well maintained an kept up-to-date. To start with I recommend you reading <a href="https://angular.io/docs/ts/latest/guide/testing.html">Testing Advanced cookbook</a>.
<br/><br/>
When starting testing a #UnitTestLackingApplication, I think tackling Angular Services are easier to start with. Why? Some services might be self contained object without dependencies, or (more frequently the only dependencies might be with http module) and for sure, there is no DOM testing needed as opposed to component testing.
<br/><br/>
<h1>Setting up tests</h1>
<br/>
I'll use the code base of openshift.io to illustrate this post. It's a big enough project to go beyond the getting started apps. Code source could be found in:
<a href="https://github.com/fabric8io/fabric8-ui/">https://github.com/fabric8io/fabric8-ui/</a>
<br/><br/>
From <a href="http://corinnekrych.blogspot.fr/2017/04/debug-my-karma.html">my previous post "Debug your Karma"</a>, you know how to run unit test and collect code coverage from Istanbul set-up. Simply run:
<br/>
<pre><code class="language-shell">npm test // to run all test
npm run test:unit // to run only unit test (the one we'll focus on)
npm run test:debug // to debug unit test
</code></pre>
<br/>
When starting a test you'll need:
<ul>
<li>to have an entry point, similar to having a <code>main.ts</code> which will call <code>TestBed.initTestEnvironment</code>,
this is done once for all your test suite. See <a href="https://github.com/corinnekrych/fabric8-ui/blob/coverage.report/config/spec-bundle.js#L37-L40">spec-bundle.js</a> for a app generated using <a href="https://github.com/AngularClass/angular-starter">AngularClass starter</a>.</li>
<li>you also need a "root" module similar (called testing module) to a root module for your application. You'll do it by using <code>TestBed.configureTestingModule</code>. This is something to do for each test suite dependent on what you want to test.</li>
</ul>
Let's delve into more details and talk about dependency injection:
<br/><br/>
<h1>Dependency Injection</h1>
<br/>
The DI in Angular consists of:
<ul>
<li><b>Injector</b> - The injector object that exposes APIs to us to create instances of dependencies. In your case we'll use TestBest which inherits from Injector.</li>
<li><b>Provider</b> - A provider takes a token and maps that to a factory function that creates an object.</li>
<li><b>Dependency</b> - A dependency is the type of which an object should be created.</li>
</ul>
Let's add unit test for Codebases service. The service Add/Retrieve list of code source. First we need to know all the dependencies the service uses so that for each dependencies we define a provider. Looking at the constructor we got the information:
<br/>
<pre><code class="language-JavaScript">@Injectable()
export class CodebasesService {
...
constructor(
private http: Http,
private logger: Logger,
private auth: AuthenticationService,
private userService: UserService,
@Inject(WIT_API_URL) apiUrl: string) {
...
}
</code></pre>
<br/>
The service depends on 4 services and one configuration string which is injected. As we want to test in isolation the service we're going to mock most of them.
<br/><br/>
<h3>How to inject mock <b>TestBed.configureTestingModule</b></h3>
<br/>
I choose to use Logger (no mock) as the service is really simple, I mock Http service (more on that later) and I mock AuthenticationService and UserService using Jasmine spy. Eventually I also inject the service under test CodebasesService.
<br/>
<pre><code class="language-JavaScript">beforeEach(() => {
mockAuthService = jasmine.createSpyObj('AuthenticationService', ['getToken']);
mockUserService = jasmine.createSpy('UserService');
TestBed.configureTestingModule({
providers: [
Logger,
BaseRequestOptions,
MockBackend,
{
provide: Http,
useFactory: (backend: MockBackend,
options: BaseRequestOptions) => new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
},
{
provide: AuthenticationService,
useValue: mockAuthService
},
{
provide: UserService,
useValue: mockUserService
},
{
provide: WIT_API_URL,
useValue: "http://example.com"
},
CodebasesService
]
});
});
</code></pre>
<br/>
One thing important to know is that with DI you are not in control of the singleton object created by the framework. This is the Hollywood concept: don't call me, I'll call you. That's why to get the singleton instance created for <code>CodebasesService</code> and <code>MockBackend</code>, you need to get it from the injector either using <code>inject</code> as below:
<br/>
<pre><code class="language-JavaScript">beforeEach(inject(
[CodebasesService, MockBackend],
(service: CodebasesService, mock: MockBackend) => {
codebasesService = service;
mockService = mock;
}));
</code></pre>
<br/>
or using <code>TestBed.get</code>:
<br/>
<pre><code class="language-JavaScript">
beforeEach(() => {
codebasesService = TestBed.get(CodebasesService);
mockService = TestBed.get(MockBackend);
});
</code></pre>
<br/>
<h3>To be or not to be</h3>
<br/>
Notice how you get the instance created for you from the injector <code>TestBed</code>. What about the mock instance you provided with <code>useValue</code>? Is it the same object instance that is being used? Interesting enough if your write a test like:
<br/>
<pre><code class="language-JavaScript">it('To be or not to be', () => {
let mockAuthServiceFromDI = TestBed.get(AuthenticationService);
expect(mockAuthService).toBe(mockAuthServiceFromDI); // [1]
expect(mockAuthService).toEqual(mockAuthServiceFromDI); // [2]
});
</code></pre>
<br/>
line 1 will fail whereas line 2 will succeed. Jasmine uses <code>toBe</code> to compare object instance whereas <code>toEqual</code> to compare object's values. As noted in <a href="https://angular.io/docs/ts/latest/guide/testing.html#!#service-from-injector">Angular documentation</a>, the instances created by the injector are not the ones you used for the provider factory method. Always, get your instance from the injector ie: TestBed.
<br/><br/>
<h1>Mocking Http module to write your test</h1>
<br/>
<h3>Using HttpModule in TestBed</h3>
<br/>
Let's revisit our TestBed's configuration to use <code>HttpModule</code>:
<br/>
<pre><code class="language-JavaScript">beforeEach(() => {
mockLog = jasmine.createSpyObj('Logger', ['error']);
mockAuthService = jasmine.createSpyObj('AuthenticationService', ['getToken']);
mockUserService = jasmine.createSpy('UserService');
TestBed.configureTestingModule({
imports: [HttpModule], // line [1]
providers: [
Logger,
{
provide: XHRBackend, useClass: MockBackend // line [2]
},
{
provide: AuthenticationService,
useValue: mockAuthService
},
{
provide: UserService,
useValue: mockUserService
},
{
provide: WIT_API_URL,
useValue: "http://example.com"
},
CodebasesService
]
});
codebasesService = TestBed.get(CodebasesService);
mockService = TestBed.get(XHRBackend);
});
</code></pre>
<br/>
By adding an <code>HttpModule</code> to our testing module in line [1], the providers for Http, <code>RequestOptions</code> is already configured. However, using an NgModule’s providers property, you can still override providers (line 2) even though it has being introduced by other imported NgModules.
With this second approach we can simply override <a href="https://github.com/angular/angular/blob/master/packages/http/src/http_module.ts#L53">XHRBackend</a>.
<br/><br/>
<h3>Mock http response</h3>
<br/>
Using Jasmine DBB style, let's test the <code>addCodebase method</code>:
<br/>
<pre><code class="language-JavaScript">it('Add codebase', () => {
// given
const expectedResponse = {"data": githubData};
mockService.connections.subscribe((connection: any) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: JSON.stringify(expectedResponse),
status: 200
})
));
});
// when
codebasesService.addCodebase("mySpace", codebase).subscribe((data: any) => {
// then
expect(data.id).toEqual(expectedResponse.data.id);
expect(data.attributes.type).toEqual(expectedResponse.data.attributes.type);
expect(data.attributes.url).toEqual(expectedResponse.data.attributes.url);
});
});
</code></pre>
<br/>
Let's do our testing using the well-know <b>given, when, then</b> paradigm.
<br/><br/>
We start with <b>given</b>: Angular’s http module comes with a testing class MockBackend. No http request is sent and you have an API to mock your call. Using <code>connection.mockResponse</code> we can mock the response of any http call. We can also mock failure (a must-have to get a 100% code coverage 😉) with <code>connection.mockError</code>.
<br/><br/>
The <b>when</b> is simply about calling our addCodebase method.
<br/><br/>
The <b>then</b> is about verifying the expected versus the actual result. Because http call return RxJS Observable, very often service's method that use async REST call will use Observable too. Here our addCodebase method return a <code>Observable<Codebase></code>. To be able to unwrap the Observable use the subscribe method. Inside it you can access the Codebase object and compare its result.<br/>
<br/><br/>
<h1>What's next?</h1>
<br/>
In this post you saw how to test a angular service using http module. You can get the <a href="https://github.com/fabric8io/fabric8-ui/blob/master/src/app/create/codebases/services/codebases.service.spec.ts">full source code in github</a>.<br/>
You've seen how to set-up a test with Dependency Injection, how to mock http layer and how to write your jasmine test.
Next blog post, we'll focus on UI layer and how to test angular component.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-65113026558034965422017-04-27T02:02:00.000-07:002017-05-19T02:04:39.932-07:00Debug my Karma I've just started getting my fingers in <a href="https://angular.io/">Angular</a>. I've worked briefly with <a href="https://angularjs.org/">AngularJS</a> in the past.<br/> Although, I'm <strong>doubtless</strong> - you know the saying: testing is doubting 😁
<br/>I've always started looking at new framework with unit testing in mind.
In this post, I'll use Google team term Angular stands for post 2.0, in there I'll use the latest release ie: 4.0.
<br/><br/>
When writing test, it is sometimes useful to debug the test using devtools (come on... no console log, vintage time is over 👾👾👾).
Let's see how to debug your test suite with Karma... It all depends on how your started your project:
<ul>
<li>either using <a href="https://github.com/angular/angular-cli">angular-cli</a></li>
<li>either using one template project like <a href="https://github.com/AngularClass/angular2-webpack-starter">angular2-webpack-starter</a></li>
<li>or what ever else, maybe all by ✋ 🤚</li>
</ul>
Let's see how to get a comfortable environment...
<br/><br/>
<h3>With angular2-webpack-starter</h3>
<br/>
Follow <a href="https://github.com/AngularClass/angular2-webpack-starter#quick-start">README instructions</a>:
<br/><br/>
<pre><code class="language-bash">git clone --depth 1 https://github.com/angularclass/angular2-webpack-starter.git
cd angular2-webpack-starter
npm install
npm test</code></pre>
<br/>
When you run it you can see a blinking browser opening, running the test and closing. To be able to debug, open <code>package.json</code> and add:
<br/><br/>
<pre><code class="language-shell">{
"name": "angular2-webpack-starter",
...
"scripts": {
"test:debug": "karma start --no-single-run --browsers Chrome",
}
}
</code></pre>
<br/>
Now just run <code>npm run test:debug</code>. Karma is now in single mode therefore Chrome stays opened!
<br/>Easy to debug simply cmd + alt + I to open devtools.
<br/>Also, note that the coverage report is ran and now visible!
<br/><br/>
<h3>With angular-cli project</h3>
<br/>
Let's open a shell, you'll need <code>node 6.5+ / npm 3+</code>, install angular-cli globally:
<br/><br/>
<pre><code class="language-shell">npm install -g @angular/cli
</code></pre>
<br/>
Create a project with angular cli and run the test:
<br/><br/>
<pre><code class="language-shell">ng new ngx-unit-test
cd ngx-unit-test
ng test
</code></pre>
<br/>
<strong>NOTE: </strong><code>npm test</code> is an alias to <code>ng test</code> (as most projects nowadays use npm script. Very handy as you don't need to globally install ng-cli!)
<br/><br/>
<code>ng test</code> will bring chrome as per default. Easy to debug simply cmd + alt + I to open devtools. Open your favourite editor, change the code. The tests are re-run.
Easy-peasy, not much to do. What about if you want to run the coverage tool?
<br/>From <a href="https://github.com/angular/angular-cli/wiki/test">angular-cli wiki</a>:
<br/><br/>
<pre><code class="language-shell">ng test --code-coverage --single-run
</code></pre>
<br/>
<h3>karma.conf.js the source of truth</h3>
<br/>
In the end, it all boils down to <code>karma.conf.js</code> configuration file. Wether by default you're running continuously in watch mode (dev friendly) or your test run with PhantomJS (CI/CD friendly), this is up to Karma configuration. It is however always good to have a build command to override and offer dev and CI friendly build.
<br/><br/>
Happy coding! Happy testing!
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-4256538584167795552017-03-14T09:40:00.001-07:002017-05-09T09:02:41.745-07:00Sharing the fun of DevNexus 20171 day of workshop, 2 days of conference sessions and so many tracks to choose from.<br />
This is <strong>DevNexus 2017</strong> in Atlanta!<br />
A fun conference to be on 🤗<br />
This year I had the pleasure to be invited as a speaker for my talk: <a href="https://www.devnexus.com/s/devnexus2017/presentations/17176">the trials and tribulations of a polyglot cross-patform mobile developer</a>.<br />
I also took the opportunities to attend as many conferences as possible. My theme this year was reactive programming.
<br />
<br />
Here is some miscellaneous notes, mumblings and souvenirs from this edition.<br />
<br />
Wednesday was workshop day. I've picked Venkat's workshop on <a href="https://www.devnexus.com/s/devnexus2017/presentations/15529">Building reactive applications</a>. As I always said: Venkat is always a good value, you never get disappointed, there is always something to learn. In this workshop, Venkat tells us what functional programming is all about: function composition and lazy evaluation. I did all the exercice with RxJS, the other guys around me used RxJava, it was funny to see how concise is the JavaScript version 😜
<br />
<br />
Thursday morning starts with Venkat's keynote on <a href="https://www.devnexus.com/s/devnexus2017/presentations/19975">Don't walk away from Complexity, Run</a>. It was a very inspirational keynote, one of my favourite quote is: <strong>Coding is not a work, coding is an addiction. </strong>👍 👍🏽 👍🏾 👍🏿
<br />
I've been addicted for 20 years and I can't get over it 😜
<br />
<blockquote class="twitter-tweet" data-lang="fr">
<div dir="ltr" lang="en">
Front raw too for <a href="https://twitter.com/devnexus">@devnexus</a> keynote. <a href="https://t.co/pukqp4oCMh">pic.twitter.com/pukqp4oCMh</a></div>
— corinne (@corinnekrych) <a href="https://twitter.com/corinnekrych/status/834770592616312832">23 février 2017</a></blockquote>
<script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script>
The next sessions I've attended:
<br />
<ul>
<li><a href="https://www.devnexus.com/s/devnexus2017/presentations/17372">Introducing TypeScript 2.0</a> by James Sturtevant: Great new addition to TypeScript 2.0 is non-nullable type, you can activate the check by adding --strictNullChecks flag to tsc command lien. To read more about non-nullable type, visit<a href="https://blog.mariusschulz.com/2016/09/27/typescript-2-0-non-nullable-types"> this blog post</a>. I like the notion of union type and the easy sugar syntax Type? for optional type (converted to union type) that reminds me Swift syntax. When used with dot operator, the optional type will need to be unwrapped.</li>
<li><a href="https://www.devnexus.com/s/devnexus2017/presentations/17442">Promises and Generators in ES6</a> by Jennifer Bland. I really like Jennifer's biography, a senior developer who was part of Lotus Domino on the AS/400 and is reconverted into JavaScript development. Our job as a developer is a continuous learning exercise.</li>
<li><a href="https://www.devnexus.com/s/devnexus2017/presentations/17329">Gradle Worst Practices: Common anti-patterns in Gradle builds</a> by Gary Hale where you learn 10 anti patterns for performance, maintenance, correctness and usability. Useful tip I'll keep in mind: make your build immutable by using use @Input / @outputDirectory annotation rather than using variables.</li>
<li><a href="https://www.devnexus.com/s/devnexus2017/presentations/17402">Overview of Webpack, a module bundler</a> by Pavan Podila: one of my favourite presentation for the rich content. I've learnt a lot, I followed Pavan as he went through <a href="https://github.com/pavanpodila/webpack-talk">his step by step github example</a>. Thanks Paven to have added the lazy loading step upon my request! I will write a separate blog post on the topic. If you work with angular2 or ReactJS, you've used webpack, you might use <a href="https://github.com/angular/angular-cli">angular-cli</a> or <a href="https://github.com/facebookincubator/create-react-app">react-create-app</a> that hides its configuration away but getting your way with plain webpack will come handy.</li>
</ul>
<br />
<br />
Friday morning keynote was all about dancing with elephants with Burr:
<br />
<blockquote class="twitter-tweet" data-lang="fr">
<div dir="ltr" lang="en">
I wonder how elephants 🐘 dance... <a href="https://twitter.com/hashtag/DevNexus2017?src=hash">#DevNexus2017</a> <a href="https://twitter.com/burrsutter">@burrsutter</a> is saying the story <a href="https://t.co/NB0iC49n29">pic.twitter.com/NB0iC49n29</a></div>
— corinne (@corinnekrych) <a href="https://twitter.com/corinnekrych/status/835132077561507840">24 février 2017</a></blockquote>
<script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script>
<br />
Keynote was followed by my ✨ presentation ✨ <a href="https://www.devnexus.com/s/devnexus2017/presentations/17176">the trials and tribulations of a polyglot cross-patform mobile developer</a>. I have great time delivering the presentation, this one was slightly different than the one I used to give: less technical but full of anecdotes and feelings. You can see my hand-crafted slides <a href="https://corinnekrych.github.io/DevNexus2017/">here</a>.
Better than thousand words, it can be all sum up with these drawings:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-HGg9WgvjMJ4Hu8Jn93I8Xl_NN8Cz3ArwoYQWQqyLF63B1e7PdPOYHE4l2QhCxN-HrGhZJBlCCW0x43ZZKku7pgrQ2aZyQHgBcsQDwJJstEwR2r9IRWNZ5oNQ4jpG2SZ8X4QI2-53Qcc/s1600/feelings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-HGg9WgvjMJ4Hu8Jn93I8Xl_NN8Cz3ArwoYQWQqyLF63B1e7PdPOYHE4l2QhCxN-HrGhZJBlCCW0x43ZZKku7pgrQ2aZyQHgBcsQDwJJstEwR2r9IRWNZ5oNQ4jpG2SZ8X4QI2-53Qcc/s640/feelings.png" width="640" /></a></div>
<br />
This is the process a developer as an early adopter goes through ^^^ <br/>
It's all about about emotions 😭 😥 😱 😂 😉 😍 <br/>
I have no doubts that sharing your feelings with you peer developers at work will make you a better communicator.
As been very often the only female developer in a team, I used to think, I’d rather not show I’m a sensitive person. It could be interpreted as a weakness.
But looking around me, I see developers (like you), people who can troll flame war on crucial subjects like tabs vs spaces in your IDE and they do it with passion and emotions. In fact, I’ve recently ran into <a href="http://uk.businessinsider.com/ceo-reveals-why-its-okay-to-show-emotion-in-the-office-2016-6?r=US&IR=T">this article from Jim Whitehurst</a>, our Red Hat CEO, where he said that “showing emotion at work is simply a reflection of a person's passion”. I couldn’t agree more with that quote.
<br/><br/>
Afternoon I've attended <a href="https://www.devnexus.com/s/devnexus2017/presentations/17440">Rx.js cleans up the async JavaScript mess</a> by @codefoster where we deep dive the <a href="https://github.com/Reactive-Extensions/RxJS/tree/master/examples/">github examples v4 on RxJS</a> and we explore the <a href="https://github.com/ReactiveX/rxjs">v5 RxJS repo</a>. <a href="https://github.com/Reactive-Extensions/RxJS/tree/master/examples/timeflies">Timeflies</a> is my favourite animation. I loves the cool effect and it reminds me <a href="http://www.bleathem.ca/blog/2015/06/rxjs-devnation.html">Brian Leathem's DevNation talk.</a> In the end, it was a great complement from Venkat's workshop and a good entry point for examples to look at. I might contribute to port those samples to version 5.
<br/><br/>
Time flies when you have fun, DevNexus 2017 was a fantastic edition, too many tracks and great subjects to choose from. I'll have to come back next year, that's for sure.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-7139194026190208352016-04-19T08:05:00.000-07:002017-04-27T02:39:54.002-07:00Watch tutorial 6: Watch Connectivity - Application ContextThis post is part of a set of short tutorials on Watch. If you want to see the <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-5-watch-connectivity.html">previous post</a>. In this tutorial, you're going to see how you can communicate between your Watch and your iOS app using Application Context.
<br/><br/>
<h2>Get starter project</h2>
In case you missed Watch tutorial 5: Watch Connectivity - Direct Message, here are the instructions how to get the starter project.
Clone and get the initial project by running:
<br />
<pre><code class="language-Shell">git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step5
open DoItCoach.xcodeproj
</code></pre>
<br/>
<h2>Send Application context from Phone</h2>
In <b>DoItCoach/DetailedTaskViewController.swift</b>, search for the method <b>timerStarted(_:)</b>, and add one line of code in [1]:
<pre><code class="language-Swift">func sendTaskToAppleWatch(task: TaskActivity) {
if WCSession.defaultSession().paired && delegate.session.watchAppInstalled { // [1]
try! delegate.session.updateApplicationContext(["task": task.toDictionary()]) // [2]
}
}
</code></pre>
[1]: Before sending to the Watch, as a best practice, check is the Watch is paired and the app in installed on the Watch. No need to do a context update when it's doomed to failure.
<br/>
[2]: You send the Context update. Here your don't try catch, but you could do it and display the error.
<br/><br/>
You need to import WatchConnectivity to make Xcode happy.<br/>
Still in <b>DoItCoach/DetailedTaskViewController.swift</b> call <b>sendTaskToAppleWatch(_:)</b> in <b>timerStarted(_:)</b> as done in [1] (Note all the rest of the method is unchanged):
<pre><code class="language-Swift">@objc public func timerStarted(note: NSNotification) {
if let userInfo = note.object,
let taskFromNotification = userInfo["task"] as? TaskActivity
where taskFromNotification.name == self.task.name {
if let sender = userInfo["sender"] as? String
where sender == "ios" {
task.start()
sendTaskToAppleWatch(task) // [1]
}
saveTasks()
self.startButton.setTitle("Stop", forState: .Normal)
self.startButton.setTitle("Stop", forState: .Selected)
self.circleView.animateCircle(0, color: taskFromNotification.type.color,
duration: taskFromNotification.duration)
}
print("iOS app::TimerStarted::note::\(note)")
}
</code></pre>
<br/>
<h2>Receive Message in Watch app</h2>
In <b>DoItCoach WatchKit Extension/ExtensionDelegate.swift</b>, at the end of the class definition, add the following extension declaration:
<pre><code class="language-Swift">// MARK: WCSessionDelegate
extension ExtensionDelegate: WCSessionDelegate {
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
if let task = applicationContext["task"] as? [String : AnyObject] { // [1]
if let name = task["name"] as? String,
let startDate = task["startDate"] as? Double {
let tasksFound = TasksManager.instance.tasks?.filter{$0.name == name} // [2]
let task: TaskActivity?
if let tasksFound = tasksFound where tasksFound.count > 0 {
task = tasksFound[0] as TaskActivity
task?.startDate = NSDate(timeIntervalSinceReferenceDate: startDate) // [3]
dispatch_async(dispatch_get_main_queue()) { // [4]
NSNotificationCenter.defaultCenter().postNotificationName("CurrentTaskStarted",
object: ["task":task!])
}
}
}
}
}
}
</code></pre>
[1]: You get the dictionary definition of the task that was started on the iPhone.
<br/>
[2]: You find its matching Task object in the list of tasks in the Watch.
<br/>
[3]: You assign the startDate defined on the iOS app.
<br/>
[4]: You make sure you go to UI thread to send a notification for the Watch to refresh its display.
<br/><br/>
<h2>Refreshing Watch display</h2>
In <b>DoItCoach WatchKit Extension/InterfaceController.swift</b> in <b>awakeWithContext(_:)</b>, add one line of code [1] to register to the event <b>CurrentTaskStarted</b>:
<pre><code class="language-Swift">override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
NSNotificationCenter.defaultCenter() // [1]
.addObserver(self,
selector: #selector(InterfaceController.taskStarted(_:)),
name: "CurrentTaskStarted",
object: nil)
display(TasksManager.instance.currentTask)
}
</code></pre>
Still in <b>DoItCoach WatchKit Extension/InterfaceController.swift</b> implement the following methods to respond to the NSNotificationCenter event:
<pre><code class="language-Swift">func taskStarted(note: NSNotification) {
if let userInfo = note.object,
let taskFromNotification = userInfo["task"] as? TaskActivity,
let current = TasksManager.instance.currentTask
where taskFromNotification.name == current.name {
replayAnimation(taskFromNotification) // [1]
}
}
func replayAnimation(task: TaskActivity) {
if let startDate = task.startDate {
let timeElapsed = NSDate().timeIntervalSinceDate(startDate)
let diff = timeElapsed < 0 ? abs(timeElapsed) : timeElapsed
let imageRangeRemaining = (diff)*90/task.duration // [2]
self.group.setBackgroundImageNamed("Time")
self.group.startAnimatingWithImagesInRange(NSMakeRange(Int(imageRangeRemaining), 90),
duration: task.duration - diff, repeatCount: 1) // [3]
}
}
</code></pre>
[1]: For the current task, replay the animation.
<br/>
[2]: Calculate how much is images is already started. You will have a short delay since the task was started in the iPhone and you received it on the Watch.
<br/>
[3]: As you've seen in Tutorial3: Animation, launch the animation.
<br/><br/>
<h2>Build and Run</h2>
You can now start a task from your phone.
The careful reader that you are, will notice that once a task started from the phone is completed, it is not refreshed on the Watch app. That brings us to the next section, let's talk about your challenges.
<br/><br/>
<h2>Challenges left to do</h2>
Your mission, should you choose to accept it is:
<ul>
<li>make the task list refreshed on the Watch when a task started from your phone get completed</li>
<li>remove the bootstrap code in <b>TaskManager.swift</b>. All tasks should be persisted to the iPhone (all the persistence code is already written for you in Task.swift). When the iPhone app launch send the list of tasks to Watch. Whenever a task is added on the phone, send the list of tasks to the watch.</li>
<li>make the animation carries on where it should be when the Watch app go background and foreground again.</li>
</ul>
<br/>
<h2>Get final project</h2>
If you want to check the final project, here are the instructions how to get it.
<br />
<pre><code class="language-Shell">cd DoItCoach
git checkout step6
open DoItCoach.xcodeproj
</code></pre>
Or if you want to get the final project with all the challenges implemented:
<pre><code class="language-Swift">cd DoItCoach
git checkout master
open DoItCoach.xcodeproj
</code></pre>
<br/>
<h2>What's next?</h2>
With this tutorial, you saw how you can send update application context messages from your Watch to your phone. Since you know how to communicate between your app and your watch, you're all ready to make great apps!CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-17327823732139134342016-04-19T08:04:00.001-07:002017-04-27T02:44:34.674-07:00Watch tutorial 5: Watch Connectivity - Direct MessageThis post is part of a set of short tutorials on Watch. If you want to see the <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-4-animation.html">previous post</a>. In this tutorial, you're going to see how you can communicate between your Watch and your iOS app.
<br/><br>
<h2>How does my Watch talk to my phone, and vice versa?</h2>
WatchConnectivity framework provides different options for implementing a bi-directional communication between WatchKit and iOS apps.
<ul>
<li><b>Application Context Mode</b>: allows exchange of data serialized in a dictionary object from one app and another. The transfer is done in the background. The messages are queued and delivered to the receiving app via a delegate method. One specificity of Application Context mode is that only the latest update is sent (ie: older data is overwritten by the new data). This is perfect if the receiving app only need the latest state.</li>
<li><b>User Information</b> transfer mode is similar to application context mode. It is also a background mode, message get queued and unlike application context all messages will be sent once the destination app is available.</li>
<li><b>Interactive messaging mode</b> sends messages (serialized in dictionary) immediately to the receiving app. The receiving app is notified of the message arrival via a delegate method call.</li>
</ul>
Whether you send a message from your iOS app or from your Watch app, the method to call is the same on both devices. Similarly when you receive a remote call the delegate method to use is the same. You'll get the "déjà vu" feeling when developing with WatchConnectivity especially for bi-directional messages.
<br/><br/>
Although, there is a symmetry of usage of WatchConnectivity framework, choosing which option to use (queued messages vs direct messages) really depends on your use case and where do you send it from. Time to dig into the nitty-gritty of Direct Messages.
<br/><br/>
<h2>Get starter project</h2>
In case you missed Watch tutorial 4: Animation, here are the instructions how to get the starter project.
Clone and get the initial project by running:
<br />
<pre><code class="language-Shell">git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step4
open DoItCoach.xcodeproj
</code></pre>
<br/>
<h2>The Use Case</h2>
Let's start using WatchConnectivity with this use case in mind. You want to start the task on your Watch and be able to see it as started on your iPhone.
From <a href="https://developer.apple.com/library/ios/documentation/WatchConnectivity/Reference/WCSession_class/#//apple_ref/occ/instm/WCSession/sendMessage:replyHandler:errorHandler:">Apple Documentation</a>:
<br/><br/>
<div style="background:LightGray;padding:1em">
Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable. Calling this method from your iOS app does not wake up the corresponding WatchKit extension. If you call this method and the counterpart is unreachable (or becomes unreachable before the message is delivered), the errorHandler block is executed with an appropriate error. The errorHandler block may also be called if the message parameter contains non property list data types.
</div>
<br/>
If I call <b>sendMessage(_:replyHandler:)</b> from my Watch, it has the ability to wake-up my iOS app. How cool!
<br/><br/>
I prefer to use Direct Messages for actions from the watch -> iPhone. The other way around iPhone -> Watch is less useful, as the chances that your Watch app is active when you send DM from your phone is slim.
<br/><br/>
Direct Message from the Watch to your Phone are useful to say "hi phone, go fetch me these resources", but bear in mind, they are not queued, so if your phone is switch off or out of range, they fail and will not be delivered.
<br/><br/>
As a rule of thumb, when synchronizing data use Application Context or UserInfo. We could have used ApplicationContext, but we'll design DoITCoach Watch App to be a companion app of the watch. All the states are persisted on the Phone.
<br/><br/>
<h2>Initialise WCSession</h2>
<b>WCSession.defaultSession()</b> returns a singleton object. You still need to define which object will handle delegate methods and activate the session.
<br/><br/>
<h3>Where?</h3>
For the Watch app, the best place to do it is <b>ExtensionDelegate.swift</b> as this is the place where the life cycle of the app takes place.
<br/><br/>
<h3>How?</h3>
In <b>DoItCoach WatchKit App Extension/ExtensionDelegate.swift</b>, add the import:
<pre><code class="language-Swift">import WatchConnectivity
</code></pre>
<pre><code class="language-Swift">var session : WCSession!
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
</code></pre>
The compiler is now complaining because your class has to implement <b>WCSessionDelegate</b>.
In <b>DoItCoach WatchKit App Extension/ExtensionDelegate.swift</b> afte3r the calss definition, add the extension declaration:
<pre><code class="language-Swift">
extension ExtensionDelegate: WCSessionDelegate {}
</code></pre>
<br/>
<h2>Send DM from Watch to Phone</h2>
In <b>DoItCoach Watch Extension/InterfaceController.swift</b> add the method to do the send:
<pre><code class="language-Swift">func sendToPhone(task: TaskActivity) {
let applicationData = ["task": task.toDictionary()]
if session.reachable { // [1]
session.sendMessage(applicationData, replyHandler: {(dict: [String : AnyObject]) -> Void in
// handle reply from iPhone app here
print("iOS APP KNOWS Watch \(dict)")
}, errorHandler: {(error) -> Void in
// catch any errors here
print("OOPs... Watch \(error)")
})
}
}
</code></pre>
At the beginning of the file add an <b>import WatchConnectivity</b>.
<br/><br/>
[1]: As a best practice, you can check the app on iOS device is reachable so you don't waste a call.
<br/><br/>
Still in <b>DoItCoach Watch Extension/InterfaceController.swift</b> call <b>sendToPhone(_:)</b> in <b>onStartButton()</b> as done in [1] (Note all the onStartButton is unchanged):
<pre><code class="language-Swift">@IBAction func onStartButton() {
guard let currentTask = TasksManager.instance.currentTask else {return}
if !currentTask.isStarted() {
let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
timer.setDate(duration)
// Timer fired
NSTimer.scheduledTimerWithTimeInterval(currentTask.duration,
target: self,
selector: #selector(NSTimer.fire),
userInfo: nil,
repeats: false)
timer.start()
// Animate
group.setBackgroundImageNamed("Time")
group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
currentTask.start()
startButtonImage.setHidden(true)
timer.setHidden(false)
taskNameLabel.setText(currentTask.name)
sendToPhone(currentTask) // [1]
}
}
</code></pre>
You also need to send a message to your phone once the task is finished in [1]:
<pre><code class="language-Swift">func fire() {
timer.stop()
startButtonImage.setHidden(false)
timer.setHidden(true)
guard let current = tasksMgr.currentTask else {return}
print("FIRE: \(current.name)")
current.stop()
group.stopAnimating()
// init for next
group.setBackgroundImageNamed("Time0")
display(tasksMgr.currentTask)
sendToPhone(current) // [1]
}
</code></pre>
<br/>
<h2>Receive Message in iOS app</h2>
<br/>
<h3>Where?</h3>
For the iOS app, the best place to do it is <b>AppDelegate.swift</b> as this is the place where the life cycle of the app takes place. You want to be able to receive direct message even when your iOS app is not started.
<br/><br/>
<h3>How?</h3>
<pre><code class="language-Swift">var session : WCSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
</code></pre>
Don't forget to import WatchConnectivity.
<br/>Déjà vu feeling?
<br/> ;)
<br/><br/>
<h3>Delegate implementation</h3>
<pre><code class="language-Swift">// MARK: WCSessionDelegate
extension AppDelegate: WCSessionDelegate {
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("RECEIVED ON IOS: \(message)")
dispatch_async(dispatch_get_main_queue()) { // [1]
if let taskMessage = message["task"] as? [String : AnyObject] {
if let taskName = taskMessage["name"] as? String {
let tasksFiltered = TasksManager.instance.tasks?.filter {$0.name == taskName}
guard let tasks = tasksFiltered else {return}
let task = tasks[0] // [2]
if task.isStarted() {
replyHandler(["taskId": task.name, "status": "already started"])
return
}
if task.endDate != nil {
replyHandler(["taskId": task.name, "status": "already finished"])
return
}
if let endDate = taskMessage["endDate"] as? Double {
task.endDate = NSDate(timeIntervalSinceReferenceDate: endDate)
replyHandler(["taskId": task.name, "status": "finished ok"])
NSNotificationCenter.defaultCenter().postNotificationName("TimerFired", // [3]
object: ["task":self])
} else if let startDate = taskMessage["startDate"] as? Double {
task.startDate = NSDate(timeIntervalSinceReferenceDate: startDate)
replyHandler(["taskId": task.name, "status": "started ok"])
}
saveTasks() // [4]
}
}
}
}
}
</code></pre>
[1]: You need to dispatch to main thread as eventually we want to refresh the UITableView in the UI queue.
<br/>
[2]: You get the task name from the dictionary. You find the matching task in iOS app (task name is used as an identifier).
<br/>
[3]: You set either startDate or endDate on the task itself. When you end the task, you need to issue an event so that UITableView get refreshed.
<br/>
[4]: You save all tasks.
<br/><br/>
<h2>Get final project</h2>
If you want to check the final project, here are the instructions how to get it.
<br />
<pre><code class="language-Shell">cd DoItCoach
git checkout step5
open DoItCoach.xcodeproj
</code></pre>
<br/><br/>
<h2>Build and Run</h2>
Before launching the app, delete any previous version of DoItCoach on your Phone.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi43uZK84foV3Fx1pE4-f_U6ax9vqYoqqlnDh0Dz-Zib33L6G3lZ362fIBd4wQgubsytb_OWS9GRPkqxXbafWXjCPKU3AqzOPlSMMtwdUQ_wVeXELXdeKd8NmnjwxM_J4-3VaB5542ycEI/s1600/step5.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi43uZK84foV3Fx1pE4-f_U6ax9vqYoqqlnDh0Dz-Zib33L6G3lZ362fIBd4wQgubsytb_OWS9GRPkqxXbafWXjCPKU3AqzOPlSMMtwdUQ_wVeXELXdeKd8NmnjwxM_J4-3VaB5542ycEI/s1600/step5.gif" /></a></div>
<br/><br/>
<br/>
<h2>What's next?</h2>
With this tutorial, you saw how you can send direct messages from your phone to your watch.
As you've seen, you can do a lot with direct message: wake up an iOS app but there are still cases where your message won't reach your phone. When it comes to synchronise states between AppleWatch and its iPhone companion app, <b>Application Context</b> or <b>User Info</b> transfer mode are much more suitable. See Watch tutorial 6: Watch Connectivity (Application Context) to learn more.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-64384582947486688262016-04-19T08:03:00.001-07:002017-04-27T02:45:49.738-07:00Watch tutorial 4: AnimationThis post is part of a set of short tutorials on Watch. If you want to see the <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-3-layout.html">previous post</a>. In this tutorial, you're going to create ring animation that looks like the one in the Activity app.
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYHoE4DOs4s7j2omnyq1UXci-2xKyLjlyJFzXaYhcAVwqlopA2-VADt6WYd70_zcC1Br5Z3ZH_11ryoN-CbfT4pr8cG5rqoVJBFqSYVlhsiVqxbRXIR2AeOLIPvms5JtHN-inEcrBPcLw/s1600/Animation.gif" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOIuquPClMfPMhaBUzuhyeKQTeS8gU7SgGbB6ulSkndzyjn6d2col9Cotw0hkqyGFM3haPH8h1vpVvWP2IHtuVdxrnQQdj_926BOkCzjwPHSLs1-RnL7uH0pCHefinpuDasNnObVx_o84/s320/watch-activity-overview-trimmed.jpg" /><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYHoE4DOs4s7j2omnyq1UXci-2xKyLjlyJFzXaYhcAVwqlopA2-VADt6WYd70_zcC1Br5Z3ZH_11ryoN-CbfT4pr8cG5rqoVJBFqSYVlhsiVqxbRXIR2AeOLIPvms5JtHN-inEcrBPcLw/s320/Animation.gif" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOIuquPClMfPMhaBUzuhyeKQTeS8gU7SgGbB6ulSkndzyjn6d2col9Cotw0hkqyGFM3haPH8h1vpVvWP2IHtuVdxrnQQdj_926BOkCzjwPHSLs1-RnL7uH0pCHefinpuDasNnObVx_o84/s1600/watch-activity-overview-trimmed.jpg" imageanchor="1"></a>
<br />
<br />
<h2>
The different types of animations</h2>
Like we've seen with Layout, animations on the AppleWatch are very simple. There are two main types of animations in WatchKit: property animations and animated images.
<br />
<ul>
<li>Properties animation is limited to some properties of of UI elements like: width/height, alpha, background color, inset.
</li>
<li>Animated images is simply a set of images. When you run them quickly, your eyes see them as animated: the basic of cartoon animation :)</li>
</ul>
<br />
<h2>
Build your images</h2>
When I first started on AppleWatch, I searched for this cool ring (that is used in Activity app) in the UI control list without success. It is not part of the start ui controls. You can do such animation but you've got to build your own imagines.
<br />
<br />
After googling, I found this interesting project: <a href="https://github.com/hmaidasani/RadialChartImageGenerator">RadialChartImageGenerator</a> which also comes with some <a href="http://hmaidasani.github.io/RadialChartImageGenerator/">online tooling available</a> to generate the images. Ah the joy of open source! Let's use the tool to generate our images.
<br />
<br />
<ul>
<li>Go to <a href="http://hmaidasani.github.io/RadialChartImageGenerator/">RadialChartImageGenerator online tool</a></li>
<li>Select the single Arc</li>
<li>For <code>Current and Max value</code> select 90</li>
<li>Select the color that match task color. You can start with dark blue and go clearer. (See image below for color reference)</li>
<li>At the bottom, untick <code>Show Text</code> and <code>Subtext</code>. You only need a empty ring set of images. As you deal with timer programmatically.</li>
<li>Hit <code>Generate Images</code> button, the images are downloaded in you download folder</li>
</ul>
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_h_KT3-LWrvXQyOLyl80ZnJYiEKnbZxpW7bowXzqGPn_kQ7Q9MXCsIhXvnpSC__orIbzKlEdKzl4pauGzZC-1WE2_ToWzzli4CjJts7sedCv1sU55iJ9wDhieb6xIrkXhyNQdEwJSVys/s1600/radial_ring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_h_KT3-LWrvXQyOLyl80ZnJYiEKnbZxpW7bowXzqGPn_kQ7Q9MXCsIhXvnpSC__orIbzKlEdKzl4pauGzZC-1WE2_ToWzzli4CjJts7sedCv1sU55iJ9wDhieb6xIrkXhyNQdEwJSVys/s1600/radial_ring.png" /></a></div>
<br />
<br />
<h2>
Get starter project</h2>
In case you missed Watch tutorial 3: Layout, here are the instructions how to get the starter project.
Clone and get the initial project by running:
<br />
<pre><code class="language-Swift">git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step3
open DoItCoach.xcodeproj
</code></pre>
<br />
<h2>
Add images to Xcode project</h2>
From the previous tutorial, you should have the animation images already populated in <b>HOME_DIR/DoItCoach/DoItCoach WatchKit App/Assets.xcassets</b> folder.
<br />
<br />
Optionally if you want to add your own ring generated images:
<br />
<ul>
<li>Create a new folder in <b>Asserts.xcassets</b>, name it TimeSpent, </li>
<li>copy your images here. You should get something like <a href="https://github.com/corinnekrych/DoItCoach/tree/master/DoItCoach%20WatchKit%20App/Assets.xcassets/TimeSpent">this</a>.</li>
</ul>
<br />
<h2>
Animate your images</h2>
You can animate all object that conform to <b>WKImageAnimatable</b>.
<br />
You are going to animate the background image of the main Group.
<br />
<br />
In DoItCoach WachKit Extension, go to <b>InterfaceController.swift</b>, go to the <b>onStartButton</b> and add the following 2 lines of code:
<br />
<pre><code class="language-Swift">group.setBackgroundImageNamed("Time")
group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentActivity.duration, repeatCount: 1)
</code></pre>
The final method should look like:
<br />
<pre><code class="language-Swift">@IBAction func onStartButton() {
guard let currentTask = TasksManager.instance.currentTask else {return}
if !currentTask.isStarted() {
let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
timer.setDate(duration)
timer.start()
currentTask.start()
group.setBackgroundImageNamed("Time")
group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
startButtonImage.setHidden(true)
timer.setHidden(false)
taskNameLabel.setText(currentTask.name)
}
}
</code></pre>
Well done! You get your timer animation done!
<br />
<br />
<h2>
Go to next task</h2>
Once the timer is fired, your Watch should display the next task on the list.
<br />
<br />
The UI <b>Timer</b> object does not provide a fire method. So to trigger an event once the the task is done, you have to add a NSTimer object.
<br />
<pre><code class="language-Swift">@IBAction func onStartButton() {
guard let currentTask = TasksManager.instance.currentTask else {return}
if !currentTask.isStarted() {
let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
timer.setDate(duration)
// Timer to fire event
NSTimer.scheduledTimerWithTimeInterval(currentTask.duration,
target: self,
selector: #selector(NSTimer.fire),
userInfo: nil,
repeats: false) // [2]
timer.start()
// Animate
group.setBackgroundImageNamed("Time")
group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
currentTask.start()
startButtonImage.setHidden(true)
timer.setHidden(false)
taskNameLabel.setText(currentTask.name)
}
}
func fire() { // [2]
timer.stop()
startButtonImage.setHidden(false)
timer.setHidden(true)
guard let current = TasksManager.instance.currentTask else {return}
current.stop()
group.stopAnimating()
display(TasksManager.instance.currentTask)
}
func display(task: Task?) {
guard let task = task else {
taskNameLabel.setText("NOTHING TO DO :)")
timer.setHidden(true)
startButtonImage.setHidden(true)
return
}
group.setBackgroundImageNamed("Time0") // [3]
taskNameLabel.setText(task.name)
}
</code></pre>
<ul>
<li>[1]: start an timer that is used to refresh UI display. Note the the <b>Timer</b> UI component can not be associated to a fires method.</li>
<li>[2]: in the fire method, you need to display start button, hide timer info, stop the animation and display the next task.</li>
<li>[3]: to initialise the Group background image to the initial image</li>
</ul>
Hooray, your timer starts, animate and go to the next task! Victory.
<br />
<br />
<h2>
Animation and Watch App life cycle</h2>
For the need of testing DoItCoach, we made the timer duration to 10 seconds. In real life, the timer would be of 25 mins.
Your AppleWatch won't say in foreground the whole duration of the task. You need to take care of replaying the animation in <code>willActivate()</code> in <code>InterfaceController.swift</code>. You will have to calculate the remaining time and make it match the image number for your animation. This is your challenge!
<br />
<br />
<h2>
Get final project</h2>
If you want to check the final project, here are the instructions how to get it.
<br />
<pre><code class="language-Swift">cd DoItCoach
git checkout step4
open DoItCoach.xcodeproj
</code></pre>
<br />
<h2>
What's next?</h2>
With this first introduction tutorial, you saw how you can do animation that look like the Activity app on your AppleWatch app. In the iOS app we can also see task in progress by selection the task in the table view. what about if the task has been started with the watch. wouldn't be nice to see it running on both AppleWatch and iOs app?
<br />
<br />
This is time to talk about WatchConnectivy! See Watch tutorial 5: Watch Connectivity (Direct Message)CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-36748204101169884822016-04-18T03:02:00.001-07:002017-04-27T02:47:05.766-07:00Watch tutorial 3: LayoutThis post is part of a set of short tutorials on Watch. If you want to see the <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-2-watch-architecture.html">previous post</a>. In this tutorial, you're going to layout the screen needed to start the task from your Watch.
<br/><br/>
<h2>Are you a AutoLayout Guru?</h2>
Good. From <a href="https://developer.apple.com/library/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/CreatingtheUserInterface.html">Apple documentation</a>:
<br/><br/>
<div style="background:LightGray;padding:1em">Watch apps do not use the same layout model used by iOS apps. When assembling the scenes for your Watch app interface, Xcode arranges items for you, stacking them vertically on different lines. At runtime, Apple Watch takes those elements and lays them out based on the available space.
</div>
<br/>
For Watch, you won't use AutoLayout!!!
<br/><br/>AppleWatch Layout is much basic and therefore much easier to use :)
<br/><br/>You must use <b>storyboards</b> to design your interfaces. Remember that storyboard is part of the <b>WatchKit app</b> bundle. Everything is laid down in storyboard, you won't be able to access a position coordinate at runtime.
To me, Watch layout looks much more like box driven ie: CSS-like rather than constraints based Layout ie: iOS-like.
<br/><br/>
<h2>Get starter project</h2>
In case you missed Watch tutorial 2: Watch Architecture, here are the instructions how to get the starter project.
Clone and get the initial project by running:
<br />
<pre><code class="language-Swift">git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step2
open DoItCoach.xcodeproj
</code></pre>
<br/>
<h2>Laying out first screen</h2>
<br/>
<h3>Add image resource</h3>
Download the Asset.assets folder from the <a href="https://github.com/corinnekrych/DoItCoach/tree/master/DoItCoach%20WatchKit%20App/Assets.xcassets">final repo</a>. In Finder copy paste <b>Assets.xcassets</b> folder into your project Watch App folder ie: <b><HOME
_FIR>/DoItCoach/DoItCoach WatchKit App/</b>.
<br/><br/>
In <b>DoItCoach WatchKit App</b> in Project Navigator, select <b>Assets.xcassets</b>, should see the new images:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcykgLfYXfDJmWKhd4vPtaAsRATBR8sMXpDoUrUKVE2873QwlQZnueatTzb416DYKj_x86BphaJ7_fO0ZMk2RwGSf6HN2-kTDKcsGXOwWchBSdm3lBmBsKHdi5HW3APeXzBcJeyQe1PHU/s1600/layout_images.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcykgLfYXfDJmWKhd4vPtaAsRATBR8sMXpDoUrUKVE2873QwlQZnueatTzb416DYKj_x86BphaJ7_fO0ZMk2RwGSf6HN2-kTDKcsGXOwWchBSdm3lBmBsKHdi5HW3APeXzBcJeyQe1PHU/s1600/layout_images.png" /></a></div>
<br/>
You're done with the images, let's go to <b>DoItCoach WatchKit App</b>'s storyboard.
<br/><br/>
<h3>Start button layout</h3>
To be able to start the timer for a task,
<ul>
<li>Go to <b>DoItCoach WatchKit App/Interface.storyboard</b></li>
<li>In the bottom right hand side Object Library search for a <b>Group</b> UI control.</li>
<li>Drag and drop the Group onto your main screen</li>
<li>In <b>Attributes Inspector</b>, choose <b>Horizontal: Center</b>, <b>Vertical: Center</b>, in <b>Background</b> select Time12 image, in <b>Height</b> select 0.9 to make sure the circle has no distortion.</li>
<li>In Object Library search for a <b>Button</b> UI control.</li>
<li>Drag and drop the Group onto your newly created group</li>
<li>In <b>Attributes Inspector</b>, select <b>Content</b> and assign the value <b>Group</b>.</li>
<li>In left hand side scene view, select the newly appeared Group below the Button, set its <b>Height</b> to <b>Relative to Container</b></li>
<li>In Object Library search for a <b>Image</b> UI control.</li>
<li>In <b>Attributes Inspector</b>, select <b>Image</b> and assign the value <b>Start</b>. Choose <b>Horizontal: Center</b>, <b>Vertical: Center</b>.</li>
<li>In Object Library search for a <b>Timer</b> UI control.</li>
<li>Drag and drop the Timer onto your Button group. Oops! what is happening, you timer is out of screen. Remember, by default Group have a horizontal layout, go to the Group under your Button and change <b>Layout</b> to <b>Vertical</b>. You can now see your timer element. For your timer, choose <b>Horizontal: Center</b>, <b>Vertical: Center</b>, <b>Hidden: true</b>, <b>Units: Second, Minute checked</b>, <b>Text Color: Green</b>, <b>Front: system, Ultra thin, 27</b>. </li>
</ul>
Build and run.
<br/>
Et voila!
<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBPE8ERbchjbRn700zwUPo_IZaarO-Btud1Gzk4fLUDa_71e2E08_WTdDI1Mw1BZ3gcm-kVMTzTQTGI2UZNDUCJ07o5f5_tfISn2BxGoxYRNX1ONVcgZGRqNuP4CNyomGtcVw71w_nwHg/s1600/step3.mp4" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBPE8ERbchjbRn700zwUPo_IZaarO-Btud1Gzk4fLUDa_71e2E08_WTdDI1Mw1BZ3gcm-kVMTzTQTGI2UZNDUCJ07o5f5_tfISn2BxGoxYRNX1ONVcgZGRqNuP4CNyomGtcVw71w_nwHg/s640/step3.mp4" /></a></div>
<br/><br/>
<h3>Add outlets</h3>
To link UI controls to your Swift code, same technique as for iOS app: use outlet and actions.
<ul>
<li>Control drag <b>Label</b> to <b>InterfaceController.swift</b>, select outlet, name it <b>taskNameLabel</b></li>
<li>Control drag <b>Group</b> (the top level one) to <b>InterfaceController.swift</b>, select outlet, name it <b>group</b></li>
<li>Control drag <b>Button</b> to <b>InterfaceController.swift</b>, select outlet, name it <b>startButton</b></li>
<li>Control drag <b>Image</b> underButton Group to <b>InterfaceController.swift</b>, select outlet, name it <b>startButtonImage</b></li>
<li>Control drag <b>Timer</b> to <b>InterfaceController.swift</b>, select outlet, name it <b>timer</b></li>
</ul>
<br/>
<h3>Add Shared business model</h3>
To add <b>Task</b> business model to <b>WatchKit App Extension</b>, go to <b>Build phases</b>, in <b>Compiled Sources</b>, add Task.swift and TasksManager.swift.
<br/><br/>
In TasksManager.swift, replace the empty <b>init()</b> by the following one to bootstrap some values into your AppWatch:
<pre><code class="language-Swift">public init() {
self.tasks = [TaskActivity(name: "Task1", manager: self), TaskActivity(name: "Task", manager: self)]
}
</code></pre>
<br/>
<h3>App life cycle</h3>
In InterfaceController.swift, add <b>display(task:)</b> method as below:
<pre><code class="language-Swift">func display(task: Task?) {
guard let task = task else { // [1]
taskNameLabel.setText("NOTHING TO DO :)")
timer.setHidden(true)
startButtonImage.setHidden(true)
return
}
taskNameLabel.setText(task.name) // [2]
}
</code></pre>
<ul>
<li>[1]: if there are no task available display in the task label: nothing to do and hide all other components</li>
<li>[2]: otherwise for a new task, display the task's name</li>
</ul>
In InterfaceController.swift, in <b>awakeWithContext(context:)</b> add a call to display method as below:
<pre><code class="language-Swift">override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
display(TasksManager.instance.currentTask)
}
</code></pre>
<br/>
<h3>Add action to Start</h3>
In InterfaceController.swift, in <b>awakeWithContext(context:)</b> add a call to display method as below:
<pre><code class="language-Swift">@IBAction func onStartButton() {
guard let currentTask = TasksManager.instance.currentTask else {return} // [1]
if !currentTask.isStarted() { // [2]
let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
timer.setDate(duration)
timer.start() // [3]
currentTask.start()
startButtonImage.setHidden(true) // [4]
timer.setHidden(false) // [5]
taskNameLabel.setText(currentTask.name)
}
}
</code></pre>
<ul>
<li>[1]: if there are no task return</li>
<li>[2]: otherwise if the task is not already started</li>
<li>[3]: start it</li>
<li>[4]: hide start button</li>
<li>[5]: show timer</li>
</ul>
<br/>
<h2>Get final project</h2>
If you want to check the final project, here are the instructions how to get it.
<br />
<pre><code class="language-Swift">cd DoItCoach
git checkout step3
open DoItCoach.xcodeproj
</code></pre>
<br/>
<h2>What's next?</h2>
With this tutorial, you saw how you can layout your first AppleWatch screen, how you connect your UI element to the code to add dynamic effect on your screen. Now it is time to talk about animation: how do you make the ring show timer progress?
<br/><br/>
See Watch tutorial 4: AnimationCorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-60364502756607849472016-04-18T03:00:00.001-07:002017-04-27T02:48:03.873-07:00Watch tutorial 2: Watch ArchitectureThis post is the second post of a set of short tutorials on Watch. If you want to see the <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-1-which-app.html">previous post</a>. In this tutorials, you're going to build your first AppleWatch app: DoItCoach.
<br />
<br />
Let's start by talking about what is an Watch app...
<br />
<br />
<h2>
How does a watch app work?</h2>
First thing to know about AppleWatch app is that is always come bundles with its companion iOS app. It's the same idea as the <a href="https://developer.apple.com/app-extensions/">App extensions</a> introduced in iOS8, where you have a main iOS app and you can add extensions. Those extensions are embedded into Today, Shared (depending on their type) component. Like Extension, for AppleWatch app, you create an app project which comes with several build targets.
<br />
<br />
A Watch app consists of two separate bundles that work together.
<br />
<ul>
<li><b>WatchKit App:</b> contains the storyboards and resource files needed to display your interface.</li>
<li><b>WatchKit Extension:</b> contains the code needed for your native AppleWatch app. This is the part to get compiled and the binaries get transferred to your watch.</li>
</ul>
<br />
<h2>
Side note: WatchOS vs watchOS2</h2>
As a side note, I think it's interesting to look back and know the differences between watchOS (the first version released in April 2015) and watchOS2 (released in September 2015). An image is worth a thousand worlds:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2Yri8pQQk2twecFR1i6XYjqcjjS8Eb9KP-4F6BYYZfu-fMNhZSM207-MzQ48LAVsVw6kuoFR6Bd8Dvl5ylSgPfdIZ218d5Oj2PmT5wS-RDJgRXo-fxMq9aZnZJYr5Go94IvFpc4iVCqg/s1600/WatchOS_vs_watchOS2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2Yri8pQQk2twecFR1i6XYjqcjjS8Eb9KP-4F6BYYZfu-fMNhZSM207-MzQ48LAVsVw6kuoFR6Bd8Dvl5ylSgPfdIZ218d5Oj2PmT5wS-RDJgRXo-fxMq9aZnZJYr5Go94IvFpc4iVCqg/s400/WatchOS_vs_watchOS2.png" /></a></div>
<br />
Now In watchOS 2, the extension runs on the user’s Apple Watch instead of on the user’s iPhone, as was the case in watchOS 1. This is the fundamental change for watchOS2: you can run watch native apps. The separation into Watch app / Watch Extension makes even more sense in the context of WatchOS1 as the binaries were deployed in different physical targets. In watchOS2, there is still the distinction of Watch app (storyboard, resources) and Watch extension (code binaries) although both are deployed natively to the AppleWatch.
<br />
<br />
The separation between WatchKit App and WatchKit Extension also means that the app's user interface is static and can't be changed at runtime. Adding or removing elements, for example, isn't possible. You can show and hide user interface elements though (I'll tell you more about that in Layout tutorial). This is done this way to save Watch resources so that it doesn't drain the battery.
<br />
<br />
Having the code binaries deployed natively makes your apps launch quicker, and be far more responsive as you remove the bluetooth latency. It also changes drastically the way you communicate/synchronize data between AppleWatch and its companion app. I tell you more about that in WatchConnectivity tutorial.
<br />
<br />
You now need to share a common business model between you iOS app and your Watch app. For that purpose, I like to separate the business model in a <b>Shared</b> group. This group is included in both iOS and Watch Extension target so it gets compiled and deployed on both.
<br />
<br />
<h2>
Shared business model</h2>
Before you start coding,have a look at the shared business model. This model is used in the iOS app and will also be used in the Watch to represent a <b>Task</b>. Looking at the Task protocol:
<br />
<pre><code class="language-Swift">public protocol Task: CustomStringConvertible {
var name: String {get}
var duration: NSTimeInterval {get}
var startDate: NSDate? {get set}
var endDate: NSDate? {get set}
var timer: NSTimer? {get set}
var type: TaskType {get set}
func start()
func stop()
...
}
</code></pre>
We see a Task has a name, a duration, a startDate and endDate and two methods to start and stop the Task.
<br />
<br />
Ready for some code?<br />
3, 2, 1... Go
<br />
<br />
<h2>
Get starter project</h2>
In case you missed Watch tutorial 1: Which app?, here are the instructions how to get the starter project.
Clone and get the initial project by running:
<br />
<pre><code class="language-Swift">git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step1
open DoItCoach.xcodeproj
</code></pre>
<br />
<h2>
Create your Watch targets</h2>
To add an AppleWatch deployment target, in Xcode:
<br />
<ul>
<li>Go to <b>File -> New -> Target...</b></li>
<li>Under <b>watchOS</b>, select <b>Application</b> tab and then choose <b>WatchKit app</b></li>
<li>In product name enter <b>DoItCoach WatchKit App</b></li>
<li>Untick all include scene, hit <b>Finish</b> button</li>
<li>Click yes when Xcode prompts you to activate Apple Watch schema</li>
</ul>
If prompted: <i>Activate “DoItCoach WatchKit App” scheme?</i>, answer yes.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHHW7wKMekrCEePb_-sGjEPDCUhoc1i2A6hvAQSLHI9b_VxmH_DBM-zcvYfH0tFvMEuQbtSXaugwUZN99hPptRsxBKmKpfLC7KDtO6gqtj79ZRJSgekFnT9gTjmu8hzfubbw7QV-GILDw/s1600/watchkitapp_target.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHHW7wKMekrCEePb_-sGjEPDCUhoc1i2A6hvAQSLHI9b_VxmH_DBM-zcvYfH0tFvMEuQbtSXaugwUZN99hPptRsxBKmKpfLC7KDtO6gqtj79ZRJSgekFnT9gTjmu8hzfubbw7QV-GILDw/s1600/watchkitapp_target.png" /></a></div>
<br />
<br />
You should now be able to see your the new target: <b>DoItcoach Watch App</b>, Xcode should have created its matching schema:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqTBluqOCtzfYRouZu9zlbUDIOuFx96rCOz0lFpXTyHWPRaoVH2QEBrH3qU2k0naOFuGlsK14_o4L-u7f80EVJM9GNdNjN5KphpI-R_E7CvQ3OG3QzXpl9mk6jEQq0SadA4lrxSv5PGu8/s1600/step1_targetcreated.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqTBluqOCtzfYRouZu9zlbUDIOuFx96rCOz0lFpXTyHWPRaoVH2QEBrH3qU2k0naOFuGlsK14_o4L-u7f80EVJM9GNdNjN5KphpI-R_E7CvQ3OG3QzXpl9mk6jEQq0SadA4lrxSv5PGu8/s640/step1_targetcreated.png" /></a></div>
<br />
<br />
You've just created your first Watch app, but if you run the appleWatch screen is all black :(
<br />
<br />
<h2>
Let's add a label</h2>
In Xcode:
<br />
<ul>
<li>Go to newly created Group named <b>DoItCoach Watch App</b></li>
<li>Select <b>Interface.storyboard</b>, in the bottom right hand side Object Library search for a <b>Label</b> UI control.</li>
<li>Drag and drop the label onto your main screen</li>
<li>In <b>Attributes Inspector</b>:
<ul>
<li>in <b>Alignment</b> section, select <b>Horizontal: center</b></li>
<li>change <b>Text Color</b> to Blue</li>
<li>in <b>Font</b> select System, UltraLight 17</li>
</ul>
</li>
</ul>
<br />
<h2>
Build and Run</h2>
<br />
<h3>
To run in the simulator</h3>
Select <b>DoItCoach WatchKit App</b> schema with <b>iPhone6sPlus + AppleWatch - 42 mm</b> as targeted simulators.
<br />
The command should start both simulators and launch the iOS app and the AppleWatch app. In Xcode, in the left hand side <b>Debug Navigator</b>, you can see debug information for each app.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8lEH4afO2_Hil69haijHobXzBwuHZCtpZ9Wp0P1cO85FC4f3q7fAO5NjmWRtiDF7H2BhNbOtbCwPwxn6YQ_LjRxeY2OUfJ3AqDXBlFzmAfS6JXh1f1rw4IJhYmxu8WoZDcR3Atqd4ha0/s1600/simulator-launch.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8lEH4afO2_Hil69haijHobXzBwuHZCtpZ9Wp0P1cO85FC4f3q7fAO5NjmWRtiDF7H2BhNbOtbCwPwxn6YQ_LjRxeY2OUfJ3AqDXBlFzmAfS6JXh1f1rw4IJhYmxu8WoZDcR3Atqd4ha0/s1600/simulator-launch.png" /></a></div>
<br />
<br />
<b>Note:</b> Sometimes, Xcode failed to attache the debug process of the iOS app, you can do it manually:
<br />
<ul>
<li>either by selecting the schema that is not launched and run it again.</li>
<li>or by manually attaching the iOS app debug process to Xcode. It quite simple and I think this <a href="https://mkswap.net/m/ios/watchkit/2015/01/11/debug-ios-app-while-watchkitapp-is-running.html">blog post</a> explained it well</li>
</ul>
<br />
<h3>
To run on Watch</h3>
When you want to run your app on Watch, plug your phone to a USB port, and keep you watch close by. You can install the app on your phone:
<br />
<li>either by selecting <b>DoItCoach WatchKit App</b> schema with your iPhone and Paired Watch. This way, you can install the app in debug mode.</li>
<li>or by selecting by selecting <b>DoItCoach</b> schema with your iPhone selected. Open Watch app, in <b>General -> App Install</b> section, make sure <b>Automatic App Install</b> is checked. Once the app is installed on your iPhone, its Watch app will be automatically installed on your Watch. With this approach you won't be able to debug your Watch app but the install might be quicker.</li>
<br />
<br />
<h2>
Get final project</h2>
If you want to check the final project, here are the instructions how to get it.
<br />
<pre><code class="language-Swift">cd DoItCoach
git checkout step2
open DoItCoach.xcodeproj
</code></pre>
<br />
<br />
<h2>
What's next?</h2>
With this first introduction tutorial, you saw how the Watch app is architectured:
<br />
<br />
To sum up, an AppleWatch project is typically build three main parts: iOS app (iOS storyboard, iOS code), Watch app (watch storyboard), Watch extension (Watch code).
<br />
<br />
You also created you first AppleWatch target, see how to build and run the apps on simulators or iPhone/paired Watch. You are now ready to add more UI controls on your watch screen. See Watch tutorial 3: Layout.CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-18227301024797402362016-04-18T02:59:00.000-07:002017-04-27T02:48:30.878-07:00Watch tutorial 1: Which app? This post is the first post of a set of short tutorials on Watch. In these step by step tutorials, you're going to build your first AppleWatch app. Yay!
<br />
<br />
I'll guide you through. No prior knowledge on watchOS2 is required, but some basic iOS development skills (storyboards usage) and Swift language knowledge are assumed.
<br />
<br />
When designing for an AppleWatch, you should keep the features simple. Bear in mind they'll have to work at on a 312 pixels wide by 390 pixels tall screen for a 42 mn watch. Don't try to fit too many features with a complexe screen hierarchy. Also, remember that your watch app comes with its iPhone companion app. Therefore, you don't need to fit all the features in the watch extension: a well chosen subset will do well.<br />
<br />
Let's start by talking about the app, you're going to build...
<br />
<br />
<h2>
DoIt Coach</h2>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga5IeA2cQMaH1CD-BI-MBM4YUQ3BQTtPv6NsgIn95kv6HymRyYkteJcJ4B5PYWlcUdZ7S_yzH5pXU1HLqCouv1Rglr0dSEBJPaIHb7byvykxsyDKZGzpafvenTfzA2a6xOadqhTSAbv-0/s1600/step0.gif" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga5IeA2cQMaH1CD-BI-MBM4YUQ3BQTtPv6NsgIn95kv6HymRyYkteJcJ4B5PYWlcUdZ7S_yzH5pXU1HLqCouv1Rglr0dSEBJPaIHb7byvykxsyDKZGzpafvenTfzA2a6xOadqhTSAbv-0/s400/step0.gif" /></a>
<br />
<br />
Have you ever wonder how to get more things done during the day, how to stay focus on your tasks? After all, getting your job done efficiently gives you more free time ;)
<br />
<br />
Based on a well known time management technique, with DoItCoach, you break your day in small tasks interlaced with small breaks. <b>DoItCoach</b>'s main goal is to be more efficient and stay healthy.
<br />
<br />
Start the day, planning the list of task to be done. For the planification use the iPhone app. Add one task followed by one break. After a 3 of those add a longer break.
<br />
<br />
Let's spice it up: since you want to stay fit in your life, you're going to try to do something physical during your breaks.
Shorter breaks could be perfect for some weight lifting or curls ;) while longer breaks could be used for outdoor walk or short run.
<br />
<br />
<h2>
The iOS app</h2>
Since the goal of this tutorial is about Watch app, you'll start with an initial project. All source code is available on github <a href="https://github.com/corinnekrych/DoItCoach">DoItCoach project</a> for the final project.
<br />
<br />
<h3>
Starter project</h3>
Clone and get the initial project by running those git commands:
<br />
<pre><code class="language-Swift">git clone https://github.com/corinnekrych/DoItCoach.git
git checkout step1
open DoItCoach.xcodeproj
</code></pre>
<h3>
Build and Run</h3>
<pre><code class="language-Swift">open DoItCoach.xcodeproj
</code></pre>
Run the project in Xcode.
<br />
<br />
You can add new task, move them and start the first task in the list. Once completed, the task is moved at the bottom of the list and a new one is available for you to start.
<br />
<br />
<h2>
What's next?</h2>
With this first introduction tutorial, you saw how the iOS app DoItCoach worked. It's now your turn to work: let's add the AppleWatch target. See <a href="http://corinnekrych.blogspot.fr/2016/04/watch-tutorial-2-watch-architecture.html">Watch tutorial 2: Watch Architecture</a>CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-80210811157817135072015-12-17T15:00:00.001-08:002015-12-17T15:01:52.684-08:00Swift runs on Linux... But what about my favourite distribution?<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtOwIkotsO7lOkCSof4kbbEbca9fC17c6A_wwpNU_TIR1krQiepiHsC9H5AjhqoqfjG6Kfkg3zNAeEHTPL6vzGpOHlepT7ESSvGbjwSak53uXsjBGz4SzhWs1uIA9yULr6xkEUMiz5-uU/s1600/xmas_fedora.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtOwIkotsO7lOkCSof4kbbEbca9fC17c6A_wwpNU_TIR1krQiepiHsC9H5AjhqoqfjG6Kfkg3zNAeEHTPL6vzGpOHlepT7ESSvGbjwSak53uXsjBGz4SzhWs1uIA9yULr6xkEUMiz5-uU/s640/xmas_fedora.png" /></a></div>
<br/><br/>
Swift is Open Source, you can install it on linux. <a href="https://github.com/apple/swift#system-requirements">Here is the instruction for an Ubuntu install</a>. But wait... My favourite distribution is Fedora!
Oh! Oh! Oh! I hear Santa Claus, I think it is time for a contribution: let's package Swift as a RPM!
<br/><br/>
<b>TL;DR: <a href="https://github.com/corinnekrych/swift-rpm/releases/tag/2.2-SNAPSHOT20151210a">Download Swift RPM from here</a></b>
<br/><br/>
<h2>Building Swift on Fedora 23</h2>
<br/>
Here is a summary of my journey in trying to build Swift on Fedora:
<ul>
<li>I've started with a brand new install of Fedora23. I've downloaded the ISO from <a href="https://dl.fedoraproject.org/pub/fedora/linux/releases/23/Workstation/x86_64/iso/">here</a> and I run the install on a Virtual Machine. </li>
<li>As stated in <a href="https://github.com/apple/swift#system-requirements">System Requirements</a>, there is a list of dependencies to fetch:</li>
<ul>
<li>for Ubuntu</li>
<pre>
cmake ninja-build clang python uuid-dev libicu-dev icu-devtools libbsd-dev libedit-dev libxml2-dev libsqlite3-dev swig libpython-dev libncurses5-dev pkg-config
</pre>
<li>which translates into Fedora platform:</li>
<pre>
cmake ninja-build clang python-devel libuuid-devel libicu-devel libbsd-devel libedit-devel libxml2-devel libsqlite3x-devel swig gcc-c++ pkgconfig
</pre>
</ul>
Notice <code>libncurses5_dev</code> and <code>icu-devtools</code> are not needed for Fedora, they seem to be fetched already either by transitive dependencies or a default of the platform.
<li>Clone Swift github repo as an entry point<li>
<pre class="objc" name="code">
git clone https://github.com/apple/swift
cd swift
./utils/update-checkout --clone
</pre>
Here I sticked to <a href="https://github.com/apple/swift#getting-sources-for-swift-and-related-projects">Swift documentation</a>. <code>update-checkout</code> is a python script that will clone all the required repositories needed to build Swift from source. It will also make sure the directory structure matches the build's need.
This is the directory structure you get:
<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirwddaNEunhjKf81Z86dtRqKGeT1YLUQy4z2tH-RSUolqxm4t4ZJZXM723V3pdWqGLiGIMpc2X2G3JG62RbX2Rq8oTmPB2Fc0TrSKThRP3ZZDVCO3_ITrOtPSLKFsDdXtwqFitAjskck8/s1600/hYLodbOdqyPWgVNzj9VZu14JYsUm6etGs4VZTRaNzIY-1.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirwddaNEunhjKf81Z86dtRqKGeT1YLUQy4z2tH-RSUolqxm4t4ZJZXM723V3pdWqGLiGIMpc2X2G3JG62RbX2Rq8oTmPB2Fc0TrSKThRP3ZZDVCO3_ITrOtPSLKFsDdXtwqFitAjskck8/s640/hYLodbOdqyPWgVNzj9VZu14JYsUm6etGs4VZTRaNzIY-1.png" /></a>
<br/>
Notice how <code>swift-lldb</code> github repo matches <code>lldb</code> directory but <code>swift-corelibs-foundation</code> stays <code>swift-corelibs-foundation</code> :]
<li>Install ninja</li>
Ninja is the current recommended build system for building Swift. It turns out that ninja's little name on Fedora is ninja-build. But CMake calls it ninja, so I've symlinked ninja to point to ninja-build:
<pre class="objc" name="code">
dnf install -y ninja-build
sudo ln -s /usr/bin/ninja-build /usr/bin/ninja
</pre>
<li>Launch the <a href="https://github.com/apple/swift#building-swift">build</a>:<li>
<pre class="objc" name="code">
./utils/build-script -t
</pre>
This is a basic build which will end up with binaries not packaged. Be aware that the build can be quite intensive on memory, I had my build crashed a couple of times. I had to restart it, but thanks goodness, it's an incremental build. On success, go to <code>../build/Ninja-DebugAssert/swift-linux-x86_64/bin</code>, you'll find <code>swiftc</code> and you can write your first swift code but... This Swift runtime is missing lots of components. For example, to build foundation:
<pre class="objc" name="code">
./utils/build-script -l -b -p --foundation
</pre>
and so on...<br/>
There must be a better way! When in doubt, use the help command is my mantra. I found <a href="https://github.com/apple/swift/blob/swift-2.2-SNAPSHOT-2015-12-10-a/utils/build-script#L222">this statement about preset mode</a>. Using <a href="https://github.com/apple/swift/blob/swift-2.2-SNAPSHOT-2015-12-10-a/utils/build-presets.ini#L463">buildbot_linux preset</a> made the trick! Thanks man.
</ul>
<br/>
<h2>Packaging in RPM</h2>
<br/>
My next step was to nicely packaged Swift into an RPM for any given Swift tag. I have to confess that I'm quite new to RPM packaging ;)
So I get inspired by existing RPM like <code>ninja-build</code>. Besides, there is tons of documentation. I've used <a href="http://www.rpm.org/max-rpm/">rpm.org</a> quite a lot.
<br/><br/>
While installing, the main issue I faced was: in Fedora, python2.7 is installed in <code>/usr/lib64</code> and the <a href="https://github.com/apple/swift-lldb/blob/master/scripts/CMakeLists.txt#L43">CMakeList.txt is looking in lib</a>, I workaround the issue<a href="https://github.com/corinnekrych/swift-rpm/blob/master/swift.spec#L46"> by overriding <code>lldb/scripts/CMakeLists.txt</code> just before building and installing</a>. Thanks <code>sed</code>. There might be better ways, I will look into making a PR to swift-lldb.
<br/><br/>
Eventually I come up with this repo: <a href="https://github.com/corinnekrych/swift-rpm">https://github.com/corinnekrych/swift-rpm</a><br/>
RPM can be directly downloading from <a href="https://github.com/corinnekrych/swift-rpm/releases">release tab</a>.
<br/><br/>
<h2>Running Swift RPM</h2>
<br/>
Last but not least, to test the RPM, I've installed a brand new Fedora VM and run the following command:
<pre class="objc" name="code">
sudo dnf install libbsd python gcc-c++ clang
sudo rpm -Uvh swift-2.2-SNAPSHOT20151210a.x86_64.rpm
</pre>
You want to see it in action?<br/>
Clone a swift repo, I used Swifter. Run:
<pre class="objc" name="code">
git clone https://github.com/glock45/swifter.git
cd swifter
swift build
</pre>
<br/>
<h2>The end</h2>
<br/>
I hope you've enjoyed my blog post. Feel free to contribute to the <a href="https://github.com/corinnekrych/swift-rpm">Swift RPM repo</a>. Let's make Fedora and Swift best friends. CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-51689575993727526862015-06-26T08:47:00.000-07:002015-06-26T08:47:38.437-07:00Playground revisited for Xcode7<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD2fG7mMOqdSDANUAswLr-jXnTMDfHM0MPqPyi5-VdVwswNFJycK5pWCQqc8Z1n8p5Ob86stRsvtHg8jhNP8vDYmzLJQC3uoCu01s9IGjAmnHuO0G6BsioJ0gwQH9j1fkq01gOdRiSsb8/s1600/plauground.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD2fG7mMOqdSDANUAswLr-jXnTMDfHM0MPqPyi5-VdVwswNFJycK5pWCQqc8Z1n8p5Ob86stRsvtHg8jhNP8vDYmzLJQC3uoCu01s9IGjAmnHuO0G6BsioJ0gwQH9j1fkq01gOdRiSsb8/s400/plauground.png" /></a></div>
Yesterday I watched WWDC 2015 <a href="https://developer.apple.com/videos/wwdc/2015/?id=405">"What's new in Playground"</a>, snuggled down in my bed, ready to enjoy my session, better than a Luc Besson's movie ;)
<br/>
(yes that's the kind of things geeks do).
<br><br/>
I use playgrounds a lot.
<br><br/>
They are part of my Swift toolbox. Good ideas found on Twitter, things I experiment, I store them in my Swift github repo. Very practical too, to share recipes with others.
<br><br/>
If we look behind for a bit of history in playground releases (not to worry Playground and Swift are just one year old, so history will be short!).
<br><br/>
<h2>Looking behind...</h2>
<br>
<b>Xcode 6.0 beta</b> brings playground. I'm convinced playgrounds have played a crucial role in the so rapid Swift adoption. Of course, playgrounds were quite buggy in beta and used to crash a lot but, with playgrounds, you can code Swift snippets, add text explanation to them to share with others. It's what the <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html">Swift guided tour</a> offered you. You can even embed images ans CSS in <i>Resources</i> folder.
<br><br>
<b>Xcode 6.2</b> brings us markdown format, see my blog post <a href="http://corinnekrych.blogspot.fr/2015/02/even-more-fun-with-playground-in-xcode.html">Even more fun with Playground</a>. To me, this was one of the biggest improvement: not to have to write fragments of explantation in HTML and then associate Swift files using some XML glue file... Markdown is just great! See my <a href="https://github.com/corinnekrych/swift/tree/xcode6.2">Swift playground in Xcode6.2 format.</a>
<br><br>
<b>Xcode 6.3</b> brings us <i>Sources</i> folder. Like we used to have the <i>Resources</i> folder. So now to be able to test a Swift framework, you can put it in Sources. No need to work with a workspace like described in <a href="http://corinnekrych.blogspot.fr/2014/08/playground-has-never-been-so-fun.html">Playground and libraries post</a>. We also have <b>inline results</b>, quite practical too. See my <a href="https://github.com/corinnekrych/swift/tree/xcode6.3">Swift playground in Xcode6.3 format.</a>
<br><br/>
<h2>So what's new in Xcode7?</h2>
<br>
<b>Xcode 7</b> brings us <b>Pages</b>.
I used to gather together all my Swift recipes in my toolbox within a workspace. With a workspace, I could also embed Swift library (prior to swift 6.3). But now with Xcode7, bye bye workspace, I'll stick to a single <i>.playground</i> file with different pages.
<br><br/>
You can navigate between pages using markdown syntax:
<pre>
[First Page](@first)
[Previous](@next)
[Previous](@previous)
[Last](@last)
</pre>
You can even go to one of your pages using its name. For a full markdown syntax go and visit <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Playground_Ref/Chapters/MarkupReference.html">Apple prerelease markdown reference page</a>.
<br><br/>
Enough talking, enough writing, let's convert my existing Swift toolbox into Swift2 and Xcode7 format. After trying the automatic Xcode Swift2 conversion (Go to: Edit -> Convert -> To Latest Swift Syntax), I gave up on that as it screwed up all my markdown and decided to migrate by hand. The code conversion was easy, the most tedious part was moving from <i>.xcworkspace</i> to one single <i>.playground</i> with multiple pages.
<br><br/>
A few minutes later....<br>
See my <a href="https://github.com/corinnekrych/swift/">Swift playground in Xcode7 format.</a>
<br><br/>
<h2>Where's to go from here?</h2>
<br/>
This year all WWDC videos are available to view to all developers, I encourage you to go through <a href="https://developer.apple.com/videos/wwdc/2015/?id=405">"What's new in Playground"</a>, a good video to enjoy with pop corn and coke.
<br/>
Another great place to look at is <a href="http://ericasadun.com/2015/06/09/swift-2-0-playgrounds-a-few-thoughts/">Erica's blog</a>, I haven't checked out her iBook on playground yet, I keep it for another great evening ;)
<br><br/>
Last, do not forget to: <br/>Practice, practice, practice.<br/>Happy Swift2 playing!CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-3796312900705720332015-06-13T07:30:00.000-07:002015-06-13T07:30:00.744-07:00RivieraDEV is over... See you all next year!2 days of sun, sea and sushi...<br/>
And great talks too!<br/>
The conference made by developers for developers with the theme "we're not only coders".
<br/><br/>
Here is some miscellaneous notes, mumblings and souvenirs from this edition.<br/> <br/>
Thursday starts with a <a href="http://www.duchess-france.org/">Duchess France</a> presentation made by <a href="https://twitter.com/bbourgois">Blandine</a>: as you've seen we not only located in Paris. If you want to get in touch, participate in such event, drop us a line on <a href="https://groups.google.com/forum/?hl=fr#!forum/duchessfr">Duchess Google group</a>.
<br/><br/>
Some Design Pattern reloaded and polyglotism latter, out for lunch with great buffet and socca!
<br/><br/>
For the afternoon, I had to miss Julien's presentation on Vert.x, as I was the speaker next room :] <br/>
I've been pleased to see a full room for my Swift presentation. Sophia Antipolis might hold more iOS/OSX developers than I thought maybe time to start a cocoaheads meetup like Florian suggested. If you're interested <a href="https://twitter.com/corinnekrych">tweet me</a>. And for those of you who wants to explore Swift in more details, here is <a href="http://corinnekrych.github.io/swift-rivieradev/assets/player/KeynoteDHTMLPlayer.html#0">my slides with more links</a>.
<br/><br/>
I also missed the sushi cooking workshop coz I went to Sebastien's talk on <a href="http://rivieradev.fr/session/68">24 mins to build a web app</a>. Although I know the talk by heart (I work with Sebi on <a href="http://aerogear.org/">AeroGear</a> project), I never miss one of his talk, it's always fun and my favorite part is the 24 mins of live coding. Today on the menu was.... ShushiApp of course.
<br/><br/>
Friday keynotes were very inspiring. From "what does innovation culture mean for Atlassian?" to "how your boost you professional karma" to end with "the code explained to my mum". I really liked Katia's talk, she's so good at story telling and the anecdote of the boomerang is very true.
<br/><br/>
<blockquote class="twitter-tweet" lang="en"><p lang="fr" dir="ltr">C'est parti pour la keynote de <a href="https://twitter.com/karesti">@karesti</a> à <a href="https://twitter.com/RivieraDEV">@RivieraDEV</a> <a href="https://twitter.com/hashtag/oratrices?src=hash">#oratrices</a> <a href="https://twitter.com/hashtag/speakeuses?src=hash">#speakeuses</a> <a href="http://t.co/zcuIjgwL0o">pic.twitter.com/zcuIjgwL0o</a></p>— DuchessFr (@duchessfr) <a href="https://twitter.com/duchessfr/status/609260222046175232">June 12, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
This edition will always be a special souvenir for me as it was he first time I've been on the other side: not only an attendee, not only a speaker but this year I was proud to wear the yellow T-shirt as part of the RivieraDEV team.
<br/><br/>
What makes a good conference is not just the great talks, the quality of the food, the fun workshops, the affordable ticket... It's all about the people you meet. With a friendly atmosphere, it's easy to talk to any body. It's the place where you learn that Vert.x or Ceylon committers are also passionate sushi cookers. You can talk to Angular committer, meet the voice behind the cast coder, chat with one of the Duchess, see Nao, talk about kids or just bump into an ex-Amadeus co-workers.
<br/><br/>
Thanks for the thanks guys, and see you all next year.
<blockquote class="twitter-tweet" lang="en"><p lang="fr" dir="ltr">Merci la team <a href="https://twitter.com/RivieraDEV">@rivieradev</a> pour cet opus 2015 <a href="http://t.co/gpMxdmFHzE">pic.twitter.com/gpMxdmFHzE</a></p>— ZePouët (@zepouet) <a href="https://twitter.com/zepouet/status/609075097748557824">June 11, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-71221765311989634252015-06-10T06:22:00.001-07:002015-06-10T06:34:38.305-07:00Swift new super power
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOqBr_l3afUbSIhT7Q6OC3Ld2EXKaWX-WNh-DIJmxGmYKi7RPT9Hr9GkZzVYVfj_eBotmA73NT6HXiGgw7Qw9fiJOqSBZHWF0OCRk9O0op_44Dq2WyppRLqP2s1zCJiTkCow0dtF1pQbM/s1600/Screenshot+2015-06-10+15.02.26.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOqBr_l3afUbSIhT7Q6OC3Ld2EXKaWX-WNh-DIJmxGmYKi7RPT9Hr9GkZzVYVfj_eBotmA73NT6HXiGgw7Qw9fiJOqSBZHWF0OCRk9O0op_44Dq2WyppRLqP2s1zCJiTkCow0dtF1pQbM/s400/Screenshot+2015-06-10+15.02.26.png" /></a></div>
<br/><br/>
To me, the most sensational news that came out of this year WWDC is without any doubt <b>Swift going Open Source</b> next Fall. Swift (the compiler and standard library) running on Linux!
<br/><br/>
I've started on Swift since day one with <a href="https://github.com/aerogear?utf8=%E2%9C%93&query=ios">AeroGear libraries</a>, switching from ObjC to Swift is quite change of paradigm. In my Swift journey, I learnt some <b>"super powers"</b> I like to talk about when I do presentations on Swift. <a href="https://speakerdeck.com/corinnekrych/switch-to-swift-presented-at-mix-it2015">See my slides for more super-hero drawings</a> ;)
<br/><br/>
Apple told us in 2014 when launching Swift:
<i>"It's Objective-C without the C"</i> and it's going to be be its successor.
In WWDC 2015, Craig Federighi said Objective-C was around for the last 30 years and Swift will be here for the next 20 years.
<br/><br/>
It turns out that Swift is not Objective-C at all, but for sure it's here to stay :]
<br/><br/>
Developers have understood it and it's no surprise to me that Swift is ranked <a href="http://redmonk.com/sogrady/2015/01/14/language-rankings-1-15/">22nd at Redmonk indice with a fulgurant growth this year</a>.
<br/><br/>
<br/><br/>
<h2>Why do I like Swift so much?</h2>
<br/>
<h3>Its elegant syntax</h3>
Just for not having to deal with block syntax agin, I love you Swift. <br/>
Some have said Swift wasn't innovative, it got lot of family ressemblance from other langages. True, it takes advantage from the experience hard-won by many other languages said Chris Lattner in his blog.
<br/>I like the consistent reusable syntax: I override subscript operator like I write computed properties etc... Easy.
<br/><br/>
<h3>Playground is just fun</h3>
Easy too, to get started with Swift. Download the Swift guided tour playground. With some REPL and hands-on, it's fun. I love how you can build your own toolbox with Swift playgrounds: mixing explanations and code snippets. See <a href="http://corinnekrych.blogspot.fr/2015/02/even-more-fun-with-playground-in-xcode.html">my previous post</a> about it and I'm happy to share <a href="https://github.com/corinnekrych/swift">my toolbox</a> with you.
<br/><br/>
<h3>Open the way to new paradigms</h3>
Swift opens the way to new paradigms: with a statically type language, generics, functions, closures, we've got the tools to do more functional programming. Immutability is right into Swift's heart. With constants and variables, Swift let you define what is immutable. Besides, almost all types in Swift are <b>value types</b>, including arrays, dictionary, numbers, booleans, tuples, and enums. Classes are the exception rather than the rule. Functional fun is not just for JVM language.
<br/><br/>
<h2>Open Source as a new super power</h2>
<br/>
For the last year, when giving presentations on Swift, I've been regularly asked: What are Apple plans on open sourcing Swift?
<br/>
At last, we've got the answer!
<br/>There is no doubt that going Open Source will fuel Swift progression, it will also, most probably open new opportunities.
<br/><br/>
Future looks bright and as a Swift developer, we can contribute.
<br/><br/>
CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com1tag:blogger.com,1999:blog-3106853329326525674.post-76647605942003890932015-04-23T07:03:00.002-07:002015-07-30T06:35:28.371-07:00How well does Swift play with iOS7?
Swift was created with the Objective-C interoperability in mind. It's easy to get why, Swift playing nicely with Objective-C, was required in order to use existing cocoa API. At first, when trying interoperability, I mostly used Objective-C libs in my Swift apps. But, as I progress in my Swift immersion, I soon write reusable Swift code. <br/><br/>
Apple stated it from day one:
<ul>
<li>you can also use Swift code from Objective-C app </li>
<li>as
Swift applications compile into standard binaries plus some Xcode bundling Swift bits in your app, you can run Swift code on iOS 7.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh05TtfzHLOldr699c_F9EtPM695kClQqpCe93NQm06i6Rxe-QEE9pdZxJjLuYHn9KRb6D0uCua1hdz9z4tUnX61JC9lgVLbG2jx0B0qGS2gVN4FtMZeldr23DBSH5uN7hHEh_QIMBmk-o/s1600/Screenshot+2015-04-23+15.51.52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh05TtfzHLOldr699c_F9EtPM695kClQqpCe93NQm06i6Rxe-QEE9pdZxJjLuYHn9KRb6D0uCua1hdz9z4tUnX61JC9lgVLbG2jx0B0qGS2gVN4FtMZeldr23DBSH5uN7hHEh_QIMBmk-o/s400/Screenshot+2015-04-23+15.51.52.png" /></a></div>
You can run Swift code in iOS7 <b>BUT</b> there are several paths to drill down…
<br/><br/>
<h1>Do you want to run a Swift app on iOS7?</h1>
<br/>
<h3>Let's talk about runtime</h3>
<br/>
How does iOS7 understand Swift? Does iOS7 operating system includes Swift support?<br/><br/>
Nope! It’s the other way around. Application with Swift code bundles Swift specific standard libs.
<br/><br/>
From <a href="https://colemancda.github.io/programming/2015/02/12/embedded-swift-frameworks-osx-command-line-tools/">Colemancda's blog post</a>:<br>
"With Swift, Apple has changed how standard libraries are shipped. With Objective-C, all of the standard libraries, system frameworks, and the runtime itself, were shipped with the OS. With Swift, <b>Apple wanted the ability to quickly deprecate parts of the Swift Standard Library</b> and also add new features. While these changes do break apps at the source code level, it would be a huge problem if shipped apps started to break because the standard library they are linked against has an incompatible API.
<b>Apple’s solution to the problem is to ship a specific version of the standard library with your app.</b>"
<br/><br/>
Besides, reading <a href="https://developer.apple.com/swift/blog/?id=2">Swift blog post about Compatibility</a>, I found that this statement is interesting:
"When the binary interface stabilizes in a year or two, the Swift runtime will become part of the host OS and this limitation will no longer exist."
<br/><br/>
iOS8 brings a shinny new langage support: Swift but, the other correlated important change that happens is the way libraries are packaged. Running Swift on iOS7 also brings the question of how well Swift/Objective-C go together.
<br/><br/>
<h3>Let's talk about Objective-C / Swift impedance</h3>
<br/>
So Swift code can be run even when called from Objective-C. Swift is a strongly type-safe language whereas Objective-C is dynamic by essence. It sometimes brings some blurry runtime behaviour (either crash or nothing happen) to watch out for when writing Swift code that aims to run on both Objective-C and Swift:<br/><br/>
<ul>
<li> Swift pure object are not supported: you need to add @objc or inherit from NSObject if your class is visible from Objective-C.</li>
<li> Pay special attention to optional. I recommend this <a href="http://stackoverflow.com/questions/26366082/cannot-access-property-of-swift-type-from-objective-c">stackoverflow post for more reading</a>.</li>
<li> Same goes when optionally casting. </li>
<!--
NSCFLocalDataTask
let downloadTask = task as? NSURLSessionDownloadTask
println("NIL?\(downloadTask)")
ios8 => NI?nil
ios7 => NIL?Optional(<__NSCFLocalDataTask: 0x145d1a50> { completed })
-->
<li> Don’t use iOS8 api: of course… it seems obvious. But it's easy to forget tough and then you run into runtime exception - I say it from experience :))</li>
<li> Some enum support is available in Objective-C since Swift1.2.</li>
</ul>
etc... I will go in more details in a later blog post.
<br/><br/>
An interesting open source library which used the Swift first approach (code written in Swift first but compatible with Objective-C) is <a href="https://github.com/Quick/Quick">Quick</a>. Most of the code is written in Swift some adapters in Objective-C are required when Swift paradigm won't fit (note: Quick and Nimble are DSL for BDD testing, DSL doe uses langage paradigm a lot).
<br/><br/>
<h3>Let's see an example</h3>
<br/>
Here is an experiment I did: Run an HelloWorld app written in Swift on iOS7. That app registers to <a href="https://github.com/aerogear/aerogear-unifiedpush-server">UnifiedPush Server</a>. For this first experiment, let's just have one application with all the source code bundled together.
<br/><br/>
You can clone the Xcode6.3 code source:
<pre class="objc" name="code">
git clone https://github.com/corinnekrych/unified-push-helloworld.git
cd unified-push-helloworld
git checkout ios7.experiment
open HelloWorldSwift.xcodeproj
</pre>
and run it.
<br/><br/>
To run the app you will need a device with iOS7 installed because push notification can not be run from simulator. Also make sure the <a href="https://ups-ckrych.rhcloud.com/ag-push">UPS instance is live on OpenShift</a>. Alternatively if my OpenShift instance is not running, create your own server following the <a href="https://aerogear.org/push/">UPS guide</a>.
<br/><br/>
Run the app on device. Go to <a href="https://ups-ckrych.rhcloud.com/ag-push">UPS console</a>, login with admin/admin. Go to "send message" right hand tab, and send a message. Your message should be displayed in the list of messages.
<br/><br/>
Now what about if we want to extract the code related to the UPS registration in an external lib?
<br/><br/>
<h1>Do you want to run Swift libs linked to Swift app on iOS7?</h1>
<br/>
<h3>Dynamic framework</h3>
<br/>
Swift libraries can only packaged using <b>dynamic framework</b> (sometimes called <b>cocoa touch framework</b> or <b>embedded framework</b> or <b>bundled framework</b>). Although dynamic frameworks are new to iOS8, they used to be used in OSX though for a while.
<br/><br/>
With Swift, you can’t package your Swift libs statically because static libs would lead to multiple runtimes in the final executable. We’re back to the point we discussed earlier in …: With Swift evolves quickly and ship its a specific version of the standard library with your app.
<br/><br/>
So you need to copy/paste your lib source code in your final app?
<br/><br/>
<h3>Cocoapods to the rescue</h3>
<br/>
Or use cocoapods 0.36+ with the <b>use_frameworks!</b> option. I recommend you to read the excellent article from Marius: <a href="http://blog.cocoapods.org/CocoaPods-0.36/">CocoaPods 0.36 - Framework and Swift Support</a>. Behind the scene, cocoapods ensures all dependant libraries are bundled together with the same set of dylibs, which are embedded into the Frameworks subdirectory of the application bundle.
<br/><br/>
Using cocoapods brings an easy tooling to support dynamic framework with Swift.
<br/><br/>
<h3>Let's see an example</h3>
<br/>
<!-- ios7-swift-ios-cocoapods -->
Let's take an simple app ChuckNorrisJoke (Yes! Chuck Norris is in the place) from <a href="https://github.com/aerogear/aerogear-ios-cookbook">aerogear-ios-cookbook</a> written in Swift and let's use <a href="https://github.com/aerogear/aerogear-ios-http">aerogear-ios-http</a> (Swift too) an run the app on iOS7.
<br/><br/>
Originally <a href="https://github.com/aerogear/aerogear-ios-http">aerogear-ios-http</a> was designed with minimal deployment target to 8.0, in this experimental branch, I'm going to lower the deployment target to 7.0 and adjust some of the Swift code to fit iOS7.
<br/><br/>
<pre class="objc" name="code">
git clone https://github.com/corinnekrych/aerogear-ios-cookbook-1
git checkout ios7.support
cd aerogear-ios-cookbook/ChuckNorrisJokes
pod install
open ChuckNorrisJokes.xcworkspace
</pre>
Run on iOS7 device or on iOS7 simulator and enjoy chuck Norris humour :)
<br/><br/>
<h1>Take away</h1>
<br/>
As we've seen, swift code can <b>run on iOS7 and iOS8</b> but comes with some <b>compromises</b>:
<ul>
<li> writing code that comply with both Objective-C and Swift. </li>
<li> dynamic framework packaging. Using cocoapods takes some of the burden away.</li>
<li> last but not least, it certainly requires some extra testing as most of the errors will happen at runtime.</li>
</ul>
Swift is moving fast, and as we've seen the latest version (Swift 1.2 with iOS that ships with iOS8.3) brings improvement for compatibility with Objective-C (enum case). Interoperability is key to achieve developer's Nirvana of "easy maintenance": write once, deploy on both iOS7 and iOS8. CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com4tag:blogger.com,1999:blog-3106853329326525674.post-85644542534123007222015-02-26T08:58:00.000-08:002015-02-27T15:46:32.421-08:00Even more fun with playground in Xcode 6.3I've just installed Xcode 6.3, it brings us even more fun with playground!<br/><br/>
Last summer, I blogged about playground and how you can use them to do great interactive tutorial.
With playgrounds... It's love at first sight. ❤ ❤ ❤ ❤
<br/></br>
I think they are great learning tools. Apple's Guided tour made them popular from day 1.
<br/><br/>
I even use them in my lib repositories to demo how to use an API. For that simply, create a workspace with your framework code and attach a playground file. See <a href="http://corinnekrych.blogspot.fr/2014/08/playground-has-never-been-so-fun.html">playground has never been so fun</a> for more details and check out <a href="https://github.com/Alamofire/Alamofire/tree/master/Alamofire.playground">Alamofire lib usage of playground</a>.
<br/><br/>
When I first gave a presentation on Swift, I decided to write it with playground of cource :P<br>
But, how to write your own guided tour?
<br/><br/>
<h3>At first, there was <b>HTML</b>...</h3>
<br/>
Playgrounds are directory that can contain: resources (images, html), swift source (.swift file) and a description file (contents.xcplayground) to help rendering.
<br/><br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiL8I4mVAdtjc3xIvh61ZZ1WKyBMCaQJy30_4PHtWEnYJ-eCFzCITyoSoAL5eqWs7tducjRDhhCdzGMOixcG_iNMmVuYa3UT4UeoBJmBS0LGj-MiXeOWZ05qCg6L6QdlKY0eimgFnsZwQ/s1600/Screenshot+2015-02-26+17.15.00.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiL8I4mVAdtjc3xIvh61ZZ1WKyBMCaQJy30_4PHtWEnYJ-eCFzCITyoSoAL5eqWs7tducjRDhhCdzGMOixcG_iNMmVuYa3UT4UeoBJmBS0LGj-MiXeOWZ05qCg6L6QdlKY0eimgFnsZwQ/s640/Screenshot+2015-02-26+17.15.00.png" /></a>
<br/><br/>
You define HTML page in <i>Documentation</i> folder, Swift source file directly under playground folder. Then using <i>contents.xcplayground</i> descriptive file you associate the different fragments together. As you're working with CSS, you can also customize you're own CSS. Don't specify too much the size etc... let Xcode preferences deal with that.
<br/><br/>
With Xcode 6.2, it renders as:
<br/><br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo58B_AqkSSWGMfdsbrZIKijg51WZp04zLbvGDGXL89-TQ_xfq8rJuYJfhTkguB45vzDWCmTDs961KqcwJKUSkbUerRLr9rDLo4FI2U1k4YFJvVnnlbIQXm99mzCzuUkatCAvoF3hM5KU/s1600/xcode_playground.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo58B_AqkSSWGMfdsbrZIKijg51WZp04zLbvGDGXL89-TQ_xfq8rJuYJfhTkguB45vzDWCmTDs961KqcwJKUSkbUerRLr9rDLo4FI2U1k4YFJvVnnlbIQXm99mzCzuUkatCAvoF3hM5KU/s640/xcode_playground.png" /></a>
<br/><br/>
The annoying part, is when you open your playground and start changing the source file section, Xcode will generate a new file number: section-1.swift will become section-2.swift and so on...<br/>
Slightly annoying, I have to confess.
<br/><br/>
<h3>Then markdown-to-playground processing</h3>
<br/>
Then emerged <a href="https://github.com/jas/playground">swift-playground-builder</a> an open source project which takes a markdown input and generates a playground out of it. And that indeed, makes your life easier, you don't have to switch between source code and documentation file. But...
<br/><br/>
I'm afraid there is a 'but'. The main drawback is: as you write your tutorial you can't check your Swift syntax. You're in markdown file!<br/>
<br/>
I personally prefer to stick to real source file and html.
<br/><br/>
<h3>To end up with markdown everywhere!</h3>
<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEKD5-3vmbChDrdnPa-RHNfV_XjAQAL6_Jenx2R5Qkoph3yLyXrNbYdlt9h97B8VRUvEEVSoQA-xtwvZtLoMoCQWHe93SIp9suzBNrycYQlTeeaVfLRSGlwYRs4L8W2Er4DsHW1B3-Azw/s1600/Screenshot+2015-02-26+17.08.55.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEKD5-3vmbChDrdnPa-RHNfV_XjAQAL6_Jenx2R5Qkoph3yLyXrNbYdlt9h97B8VRUvEEVSoQA-xtwvZtLoMoCQWHe93SIp9suzBNrycYQlTeeaVfLRSGlwYRs4L8W2Er4DsHW1B3-Azw/s640/Screenshot+2015-02-26+17.08.55.png" /></a>
<br/><br/>
With markdown directly in swift source code, you can write source code and tutorial text at the same time by using special comment ```//:``` :
<pre class="objc" name="code">
//: ### Immutability
//: ```let``` for constants => cannot change once initialized<br/>
//: ```var``` for variable => can be changed and can be optional.
let name = "julie"
let age = 18
println("Hello my name is \(name) and I am \(age) years old")
</pre>
And it renders as:
<br/><br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEircs_KWwal9X_n3u2lkiwx3mkbEOQ3PMODO6MYZ_nPPqQcQSAtpOl_qqliTS_M9F6vhj581dudzA_A4A2Rx7EuuWP8rkQJPf4PYiWeka8oE25kp_m6w9VRZ-3e1sN73C6wTcsptm25mog/s1600/xcode_playground.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEircs_KWwal9X_n3u2lkiwx3mkbEOQ3PMODO6MYZ_nPPqQcQSAtpOl_qqliTS_M9F6vhj581dudzA_A4A2Rx7EuuWP8rkQJPf4PYiWeka8oE25kp_m6w9VRZ-3e1sN73C6wTcsptm25mog/s640/xcode_playground.png" /></a>
<br/><br/>
The only slight 'but' here is about refreshing...<br>
Difficult to write your comment in Xcode directly and have them refreshed. I usually work with Xcode opened for rendering and another editor for editing, triggering refreshing but switching Xcode current file. Caution when modifying <i>contents.xcplayground</i>, Xcode is picky on this and may get upset (yeah! good old Xcode crash are not gone!)<br><br>
Writing interactive tutorial is really easy with Xcode 6.3. Follow the links if you want to see the source code of the Swift tutorial I've talked about in <a href= "https://github.com/corinnekrych/swift/tree/xcode6.2">xcode 6.1 format</a> or with <a href="https://github.com/corinnekrych/swift/">the latest 6.3 format</a>.<br/>
Happy Swifting!CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-76498615770900638692015-01-12T12:48:00.001-08:002017-04-27T02:54:57.968-07:00Sharing Keychain access in a Share ExtensionI've been wanted to do a blog post on how to achieve SSO on iOS using sharing Keychain for a bit...
<br/><br/>
And at the same time, I also wanted to try app extension very badly. So in an attempt to get the best of the two worlds, let's talk about writing a share extension to an app which need to store OAuth2 access token in a secure manner. We'll see how to share Keychain content through group-id between an app and its extension.
<br/><br/>
Remember Shoot'nShare app?<br/>
A simple app that takes pictures and allows you to share them with Facebook, GoogleDrive or even your own Keycloak backend. If we want to learn more about it, visit previous blog posts:
<ul>
<li><a href="http://corinnekrych.blogspot.fr/2014/10/aerogear-with-keycloak-oauth2-friends.html">AeroGear OAuth2 and Keycloak</a></li>
<li><a href="http://corinnekrych.blogspot.fr/2014/11/oauth2-for-android-and-ios-with-keycloak.html">AeroGear OAuth2 Shoot demo</a></li>
</ul>
To simplify, in this blog post we will focus on sharing to Google Drive only. As a pre-requisite, let's start creating a Google project.
<br/><br/>
<h1>OAuth2 Google Set up</h1>
<br/>
If you want to create a google project to use for uploading files to Google Drive, follow the steps below:
<ul>
<li>Have a Google account</li>
<li>Go to <a href="https://console.developers.google.com/project">Google cloud console</a>, create a new project</li>
<li>Go to APIs & auth menu, then select APIs and turn on Drive API</li>
<li>Always in APIs & auth menu, select Credentials and hit create new client id button Select iOS client and enter your bundle id.</li>
NOTES: Enter a correct bundle id as it will be use in URL schema to specify the callback URL. Please use your own unique BUNDLE_ID with format like org.YOUR_DOMAIN.Shoot replacing YOUR_DOMAIN with your actual domain.
</ul>
Once completed you will have your information displayed as below:<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEyzYeC8GF-HhIjB29mBdqZbx4p201i7QkbjbQ9ugHPMwTuWc0gR7iVQbP6jHdZHRK9rcpdyDthFkWHB8S-dGUX9_vwWfWc0HS3bKIvmXQNIdJCFDzq6qLbUSZ0joduM1ZBw8Dnfu97_M/s1600/shoot_google_cloud_admin.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEyzYeC8GF-HhIjB29mBdqZbx4p201i7QkbjbQ9ugHPMwTuWc0gR7iVQbP6jHdZHRK9rcpdyDthFkWHB8S-dGUX9_vwWfWc0HS3bKIvmXQNIdJCFDzq6qLbUSZ0joduM1ZBw8Dnfu97_M/s640/shoot_google_cloud_admin.png"></a></div>
Now that we've got your google project set up, let's add an Share Extension to Shoot app and see what's involved.
<br/><br/>
<h1>Share Extension</h1>
<br/>
<h2>What it is?</h2>
An App extension add feature to an existing application.
There are several types of extensions. The one we're interested in today is the <b>share extensions</b>. As the name says it all, this extension lets you share content with the external world. By default Xcode template will inherit from <i>SLComposeServiceViewController</i>. Therefore when hitting <i>share</i> button, a pop-up appears to send a message with image. Before iOS8, only a handset of providers were available to share content with. Those providers were defined directly in the operating system directly so the list was not flexible at all. Those days are over (yay!), you can now share with your favourite or even your own social networks directly from <b>Photos</b> app. This is exactly what we're going to do: let's share to GoogleDrive from Photos app via Shoot'nShare app.
<br/><br/>
One important thing to bear in mind extensions are not deployed by themselves. They must be packaged within a container app. Concretely in Xcode extensions are extension target within you container app.
<br/><br/>
<h2>Let's see an example</h2>
<b>1. Get the project</b><br/>
Code source can be found in <a href="https://github.com/aerogear/aerogear-ios-cookbook/tree/AGIOS-224.shoot-extension">aerogear-ios-cookbook app.extension branch</a>. Clone the repo and select the correct branch:
<pre><code class="language-Swift">git clone git@github.com:aerogear/aerogear-ios-cookbook.git
git checkout AGIOS-224.shoot-extension
</code></pre>
<b>2. Define you own bundle_id</b><br/>
To be able to work with extension you need to enable <b>App Groups</b>. App Groups are closely linked to bundle identifiers. So let's change the BUNDLE_ID of the project to match your name. Select the Shoot project in the Project Navigator, and then select the Shoot target from the list of targets. On the General tab, update the Bundle Identifier to org.YOUR_DOMAIN.Shoot replacing YOUR_DOMAIN with your actual domain. Do the same for the extension target: select the Shoot project in the Project Navigator and then select the ShootExt target. On the General tab, update the Bundle Identifier to org.YOUR_DOMAIN.Shoot.ShootExt replacing YOUR_DOMAIN with your actual domain.
<br/><br/>
<b>3. Configure App Group for Shoot target</b><br/>
In order for Shoot'nShare to share content with its extension, you’ll need to set up an <b>App Group</b>. App Groups allow access to group containers that are shared amongst related apps, or in this case your container app and extension. Select the Shoot project, switch to the Capabilities tab and enable App Groups by flicking the switch. Add a new group, name it group.org.YOUR_DOMAIN.Shoot, again replacing YOUR_DOMAIN with your actual domain.
<br/><br/>
<b>4. Configure your App Group for ShootExt target</b><br/>
Open the Capabilities tab and enable App Groups. Select the group you created when setting up the Shoot project. The App Group simply allows both the extension and container app to share files.
This is important because of the way files are uploaded when using the extension. Before uploading, image files are saved to the shared container. Then, they are scheduled for upload via a background task.
<br/><br/>
<h1>Sharing Keychain</h1>
<br/>
With the same idea of sharing group between apps (or app and extension) to be able to have a common space for saving files, we can use Keychain group so that app and extension can share Keychain items. In our case we want a common space for Shoot app and Shoot Ext to share OAuth2 access token.
<br/><br/>
<b>1. Configure Keychain Sharing for Shoot target</b><br/>
In order for Shoot'nShare to share access tokens with its extensions, you’ll need to set up a Keychain Sharing Group. Select the Shoot project in the Project Navigator, and then select the Shoot target from the list of targets.
Now switch to the Capabilities tab and enable Keychain Sharing by flicking the switch. Add a new group, name it org.YOUR_DOMAIN.Shoot, again replacing YOUR_DOMAIN with your actual domain.
<br/><br/>
<b>2. Configure Keychain Sharing for ShootExt target</b><br/>
Select the Shoot project in the Project Navigator and then select the ShootExt target. Open the Capabilities tab and enable Keychain Sharing. Select the group you created when setting up the Shoot project.
<br/><br/>
<b>3. Configure Shoot App code</b><br/>
In Shoot/ViewController.swift modify:
<pre><code class="language-Swift">@IBAction func shareWithGoogleDrive() {
let googleConfig = GoogleConfig(
clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
scopes:["https://www.googleapis.com/auth/drive"])
let ssoKeychainGroup = "YOUR_APP_ID_PREFIX.org.YOUR_DOMAIN.Shoot"
...
</code></pre>
where YOUR_APP_ID_PREFIX is a unique alphanumeric identifier, you can view it on dev center:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoYd2dvAvACRFavdqMieAaIaBelJdjFxoMN8zERQoUvduwlhli4meShr5Z-AfSDmDCjIK9Ef6Jku2Zs6W_oiz5BdRLNFJ0TqfXaPWWK7VBJm69MR2XQP6HDXNviWTEe6RCRhaOGSs-PkQ/s1600/Screenshot+2014-12-18+09.29.13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoYd2dvAvACRFavdqMieAaIaBelJdjFxoMN8zERQoUvduwlhli4meShr5Z-AfSDmDCjIK9Ef6Jku2Zs6W_oiz5BdRLNFJ0TqfXaPWWK7VBJm69MR2XQP6HDXNviWTEe6RCRhaOGSs-PkQ/s640/Screenshot+2014-12-18+09.29.13.png"></a></div>
and org.YOUR_DOMAIN.Shoot is you BUNDLE_ID.
<br/><br/>
<b>4. Configure ShootExt code</b><br/>
In ShootExt/ViewController.swift modify:
<pre><code class="language-Swift">let ssoKeychainGroup = "357BX7TCT5.org.corinne.Shoot"
let appGroup = "group.org.corinne.Shoot"
override func didSelectPost() {
// We can not use googleconfig as per default it take your ext bundle id, here we want to takes shoot app bundle id for redirect_uri
let googleConfig = Config(base: "https://accounts.google.com",
authzEndpoint: "o/oauth2/auth",
redirectURL: "org.YOUR_DOMAIN.Shoot:/oauth2Callback",
accessTokenEndpoint: "o/oauth2/token",
clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
refreshTokenEndpoint: "o/oauth2/token",
revokeTokenEndpoint: "rest/revoke",
scopes:["https://www.googleapis.com/auth/drive"])"
...
</code></pre>
Replace:<br>
the constant ssoKeychainGroup with your YOUR_APP_ID_PREFIX + BUNDLE_ID.<br/>
the constant appGroup with your App Group<br/>
in google config, redirectURL should match your BUNDLE_ID<br/>
<br/>
<b>5. Run the extension</b><br/>
To run shoot extension, select ShootExt target and run it, select Photos app as host app.<br/>
Select a photo, click on share button and select Shoot app. A Pop-up will appear, select send: you photo is uploaded on the background... and we're done. We've done all the configuration needed. Let's look at the code now.
<br/><br/>
<h1>Spot the Difference</h1>
<br/>
Actually what we want to do from Share extension is basically the same as we do from Shoot'nShare app. But we do in an extension to allow us to do from Photos app. What about playing the difference game? What are the differences between uploading from Shoot'nShare app or uploading from ShootExt?
<br/><br/>
<b>1. you can not trigger the OAuth2 danse from the extension</b><br/>
Extensions have limitations. Some API are not available. An app extension cannot access a sharedApplication object, and so cannot use any of the methods on that object. Difficult to trigger an external browser to launch the OAuth2 danse. Opening the container app in case no access tokens is available could be an alternative... However this alternative is offered only for today widget extension...
<br/><br/>
Indeed depending on extension type, some actions are allowed or forbidden. For example quoting<a href="https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html#//apple_ref/doc/uid/TP40014214-CH2-SW2"> apple doc</a> : "only a today widget (<b>and no other app extension type</b>) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class."
<br/><br/>
With our ShootExt, it would have been handy to be able to open Shoot'nShare app if no access token is available in the shared keychain. As our extension is a share extension, this is not available. As a result, we take as a pre-requisite that the end user has already shared a photo from Shoot'nShare app before using the extension. To do so we override OAuth2Module's requestAuthorizationCode method:
<pre><code class="language-Swift">public class OAuth2ModuleExtension: OAuth2Module {
// For extension we do not want to be redirected to browser to authenticate
// As a pre-requisite we should have a valid access_token stored in Keychain
override public func requestAuthorizationCode(completionHandler: (AnyObject?, NSError?) -> Void) {
completionHandler("NO_TOKEN", nil)
}
}
</code></pre>
In case there is no token, we will return an error message to the end user asking him to use Shoot'nShare first.
<br/><br/>
<b>2. you use the same redirect-uri OAuth2 for both extension and app</b><br/>
To do so, in ShootExt/ViewController.swift we can not use GoogleConfig class, we'll have to use Config class ans spscify shoot'nShare's redirect url as shown below:
<pre><code class="language-Swift">let googleConfig = Config(base: "https://accounts.google.com",
authzEndpoint: "o/oauth2/auth",
redirectURL: "org.YOUR_DOMAIN.Shoot:/oauth2Callback",
accessTokenEndpoint: "o/oauth2/token",
clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
refreshTokenEndpoint: "o/oauth2/token",
revokeTokenEndpoint: "rest/revoke",
scopes:["https://www.googleapis.com/auth/drive"])
</code></pre>
<br/><br/>
<b>3. you need to save in the Keychain using group-id</b><br/>
When we first save the access token in Shoot'nShare app, we need to specified the group-id, in Shoot/Viewcontroller.swift, we modify shareWithGoogleDrive method to accomodate it: In line7-10 we create a TrustedPersistantOAuth2Session object with a keychain group-id:
<pre><code class="language-Swift"> @IBAction func shareWithGoogleDrive() {
let googleConfig = GoogleConfig(
clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
scopes:["https://www.googleapis.com/auth/drive"])
let ssoKeychainGroup = "YOUR_APP_ID_PREFIX.org.YOUR_DOMAIN.Shoot"
// We specify the keychain groupId, should be the same as the one used in Share extension
let gdModule = OAuth2Module(config: googleConfig,
session: TrustedPersistantOAuth2Session(accountId:
"ACCOUNT_FOR_CLIENTID_\(googleConfig.clientId)",
groupId: ssoKeychainGroup))
self.http.authzModule = gdModule
self.performUpload("https://www.googleapis.com/upload/drive/v2/files", parameters: self.extractImageAsMultipartParams())
}
</code></pre>
<br/><br/>
<b>4. you upload your photo in the background</b><br/>
Last but not least, when dealing with extension, remember that action will take place in the background!
In our case we want to perform a multipart upload (we're using multipart Google endpoint) in the background. using <a href="https://github.com/aerogear/aerogear-ios-http">aerogear-ios-http</a> you can perform multipart background upload either using upload method with stream or file as shown line 28. It's also possible to use a POST method with multipart params (behind the scene a NSURSLSession upload is performed):
<pre><code class="language-Swift"> override func didSelectPost() {
let googleConfig = ....
// Create a TrustedPersistantOAuth2Session with a groupId for keychain group sharing
let gdModule = OAuth2ModuleExtension(config: googleConfig, session:
TrustedPersistantOAuth2Session(accountId: "ACCOUNT_FOR_CLIENTID_\(googleConfig.clientId)",
groupId: ssoKeychainGroup))
self.http.authzModule = gdModule
gdModule.requestAccess { (response: AnyObject?, error: NSError?) -> Void in
var accessToken = response as? String
if accessToken == "NO_TOKEN" {
println("You should go to Shoot app and grant oauth2 access")
} else {
let imageURL = self.saveImage(self.imageToShare!, name: NSUUID().UUIDString)
// multipart upload
let multiPartData = MultiPartData(url: imageURL!,
mimeType: "image/jpg")
let parameters = ["file": multiPartData]
// multi-part upload could be achievd either with upload as a stream or using POST
self.http.upload("https://www.googleapis.com/upload/drive/v2/files",
stream: NSInputStream(URL: imageURL!)!,
parameters: parameters,
method: .POST,
progress: { (ar1:Int64, ar2:Int64, arr3:Int64) -> Void in
println("Uploading...")
}) { (response: AnyObject?, error: NSError?) -> Void in
println("Uploaded: \(response) \(error)")
}
}
}
}
</code></pre>
<br/><br/>
Hope your find this blog post useful and remember: it's all about Sharing...<br/>
Do not hesitate to share this link :)CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com0tag:blogger.com,1999:blog-3106853329326525674.post-3837028849437253222014-11-15T05:30:00.000-08:002014-11-16T02:52:47.945-08:00Devoxx 2014 in the eyes of a mobile developer<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXqnDjGzSS72gfwNq54gKpgg_30v-aQgdls9iKVEcoLWMEDiQfgvjEzRqBNM7WAxRoo2dh6El8fUZJkz3BK7nz4RgqWHIfpst3E6nTWuFkN5za5EiqrloNzLbVoyrUV1PlMiL9f5sgc54/s1600/6-Gemstones-That-Can-Be-Used-As-Substitutes-For-Diamond.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXqnDjGzSS72gfwNq54gKpgg_30v-aQgdls9iKVEcoLWMEDiQfgvjEzRqBNM7WAxRoo2dh6El8fUZJkz3BK7nz4RgqWHIfpst3E6nTWuFkN5za5EiqrloNzLbVoyrUV1PlMiL9f5sgc54/s400/6-Gemstones-That-Can-Be-Used-As-Substitutes-For-Diamond.jpg" /></a></div>
This year was my first Devoxx edition. On Sunday evening, going out of Antwerpen central station, I'm struck by all those sparkling shops on Keyserlei main road: diamonds all over the place! <br/><br/>
As I’ll learn it later, Antwerpen is a well-known place for its diamond commerce.<br/><br/>
Here is a summary of this Devoxx 2014 cru. As a disclaimer, I shall say that although I’ve been a Java guy for many years as a mobile developer, I very often do JavaScript and when going native, my platform of heart is not Android but iOS… As <a href="https://twitter.com/@tedneward">Ted</a> said in his Swift for busy Java developer: I’m gonna tell you about Swift wearing my .NET T-shirt in a Java conference, it’s how it is nowadays… <br/><br/><br/>
With this in mind, I’d like to share with you my take-away sessions:<br/><br/>
- “<a href="http://cfp.devoxx.be/2014/talk/PYQ-0556/Using_Traits,_Mixins_and_Monads_in_JVM_Languages">Using Traits, Mixins and Monads in JVM Languages</a>”. What I like best about Venkat’s talks is how he goes from “what's the problem?” to the different solutions implemented in several languages (true polyglotism). And the problem starts with … diamond! (remember this old diamond issue in C++). Through live coding examples in Scala And Groovy we explore how to overcome the inheritance collidance with collaboration chaining behaviour like train wagons.
Still on Monad, the very popular session “<a href="http://cfp.devoxx.be/2014/talk/HZZ-6927/What_Have_the_Monads_Ever_Done_For_Us%3F">What Have the Monads Ever Done For Us?</a>” (by Dick Wall) is the place where you learn you don’t need to know about Monads to use them. But to sound smart this winter, powder your tech chat with monoid, functor and monad off course (could be really handy as you talk about Swift actually).
<br/><br/>
- Swift made some buzz this year with 2 main sessions and being mentioned on several others: <a href="http://cfp.devoxx.be/2014/talk/GDL-6943/Swift_for_Java_Developers">an university walk-through with interactive playground</a> where Mike drives us through the intricacies of the language with playground samples. “<a href="http://cfp.devoxx.be/2014/talk/BDL-3486/Busy_Java_Developer's_Guide_to_Apple's_Swift">Busy Java Developer's Guide to Apple's Swift</a>” takes the same playground format with a very personalised style. Yes no cool CSS in playground but plenty of geek humour :) As I didn’t find any of those playground source code <a href="https://github.com/corinnekrych/swift">here is the link to one of mine</a>.
<br/><br/>
- Develop your mobile app once in your favourite language and deploy it on iOS and Android. Still the Nirvana mantra for quick-win mobile development…
If your language of choice is Java, see “<a href="http://cfp.devoxx.be/2014/talk/BIU-5977/Real_cross-platform_Java_on_mobile_devices">Real cross-platform Java on mobile devices</a>” session and how to use JavaFX to build your app on Android (LodgON) and iOS (RoboVM). UI is not yet native feel but it’s in the pipe for future as well as using Java8.
If you’re a JavaScript ninja, go to “<a href="http://cfp.devoxx.be/2014/talk/BEM-4618/Use_JavaScript_to_build_Mobile_Apps_with_Native_UI">Use JavaScript to build Mobile Apps with Native UI</a>” session to hear about Titanium. You have to learn a titanium specific API to build all your app including UI. but, UI get bridged into native widgets. As … stresses a lot no lowest common denominator, When you need to go platform specific use a if/else :))
<br/><br/>
- “<a href="http://cfp.devoxx.be/2014/talk/REH-2781/Testing_your_Android_app">Testing your Android app</a>” to go through all testing layers from unit testing to instrumentation testing. Followed by “<a href="http://cfp.devoxx.be/2014/talk/ZHZ-1981/Espresso:_What_else%3F">Espresso: What else?</a>” makes me feel… jealous: so many testing frameworks for Android and soon Espresso part of development toolkit.
<br/><br/>
- Wednesday keynotes was done by Red Hat and our team work was show cased #ProudAeroGearDeveloper, let’s see it in picture:
<br/><br/>
<blockquote class="twitter-tweet" lang="fr"><p><a href="https://twitter.com/AeroGears">@AeroGears</a> UPS with FeedHenry show cased <a href="https://twitter.com/hashtag/Devoxx?src=hash">#Devoxx</a> <a href="http://t.co/jtFOiScWDC">pic.twitter.com/jtFOiScWDC</a></p>— corinne (@corinnekrych) <a href="https://twitter.com/corinnekrych/status/532468695818567680">12 Novembre 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
and if you want to read more about it, see <a href="http://blog.eisele.net/2014/11/technical-details-about-the-jboss-devoxx-demo.html">Markus’ blog</a>.
<br/><br/>
- On security topic, I really enjoyed “<a href="http://cfp.devoxx.be/2014/talk/WUY-5284/Death%20to%20Cookies,%20Long%20Live%20JSON%20Web%20Tokens">Death to Cookies, Long Live JSON Web Tokens</a>” from Auth0 team where you learn how to decode a json web token with utilities like jwt.io
<br/><br/>
- My favourite Java8 talk focuses on hidden gems rather than shining Stream and Lambdas. "<a href="http://cfp.devoxx.be/2014/talk/QCC-4933/50_new_things_we_can_do_with_Java_8">50 new things we can do with Java 8</a>": Let's ends this post with José Paumard final word: Diamonds are developer's best friends.
<br/><br/>
I couldn't agree more!<br/>
<br/>
Corinne
<br/>
PS: not to forgot the reason why I was there: my tools in action session <a href="http://cfp.devoxx.be/2014/talk/MBH-2111/OAuth2%20for%20native%20apps">OAuth2 for native apps</a> ;)CorinneKrychhttp://www.blogger.com/profile/15012920607099735185noreply@blogger.com1