Demo app of IONIC v1 and Firebase v3
This is a demo app to apprehend IONIC v1 (which is built upon Cordova) and Firebase v3.
Scope : iOS 8+ / Android 4.4+
I don’t think I will develop new UI functionalities from this point. It is a simple demo. In a normal app, some UI choices would have been different and of course the app would have been richer but I wanted to illustrate some components and keep it simple. Detail screens are limited in informations by necessity because the Marvel API is a tad poor in details.
Crosswalk is used as the default android browser, thus allowing us to share the same engine for all platforms. It give us control on that engine. We can update it whenever we want and have always the latest innovations from Chromium. The downside is a 25mb overload to the apk size.
On iOS land, WkWebView is not used yet because it is too young. WkWebView should be handled natively in Ionic v2.
Quick readers can do npm start
in order to check it quickly :
I do not recommend to develop in the browser. It is different than a real device. You should develop on a real device and use the browser solution when it makes sense to make your life simpler for particular cases.
In order to test in a simulator/emulator/device, you will need first to install everything needed by cordova and ionic (sdk, emulators, etc.) and then hit ionic state restore
in your terminal which will install all cordova platforms and plugins. See prerequisites section.
Then you just need to use one of the predefined npm alias. For instance: npm run android
to deploy on a device/emulator.
Complete build powered by grunt and npm:
Travis CI:
.travis.yml
fileGreenhouse CI:
Coverage report on codecov.io
Cordova, Ionic, Bower and Grunt should be installed globally, they will delegate to a local instance if any:
npm i -g cordova ionic bower grunt-cli
iOS deployment tools should also be installed globally as recommended by Ionic for practical reasons too:
npm i -g ios-sim ios-deploy
XCode should be installed from the Mac App Store and launched at least once.
Android Studio and sdk should be installed. Android tools should be in path.
You may install and configure emulator images if you want to run this project in an emulator.
JDK 7 (or more recent) should be installed.
This project should work on all versions of node since 0.12.7. I recommend NVM for simple and powerful node management.
This project uses Grunt as a task manager. Grunt tasks are lazy-loaded for performance.
Tasks are defined inside grunt
folder. There is one file per type of task. For instance, css.js
contains css related tasks.
It allows to have all related tasks in one file without having a too bigger file.
I found it to be a good compromise between the one-file-per-task and one-big-monolithic-file strategies.
Grunt is used essentially to build web code and start tools.
This project is using jshint (see .jshintrc
) and jscs (see .jscsrc
) to validate both form and content.
This project tries to respect guidelines from Angular team.
I have tried to use super easy and known optimisations like:
An hybrid app should be very optimized (especially when there is animations) regarding today browser performances which are a lot inferior
to native code. It means also each functionality should be judged with caution in regard of added value, code complexity and performance.
SASS
is used to build upon Ionic SASS code and allow easy UI customisations.
_ionic.app.scss
is reserved for ionic customization like when we want to override default colors, or default font sizes, etc.
_layout.scss
should contain transverse layout definitions.
app.scss
is our entry point. You probably will never have to modify it. It is responsible to import everything else.
When writing a new scss file for a component you should always prefix it with _
so we know it is a partial. This is convention in sass world. All partials are automatically imported by app.scss
when building. See grunt/css.js
.
I always liked css object oriented features but they come with the cost of performance and maintainability.
I tried a BEM custom approach which seems to be a good compromise :
Nonetheless, it is not a religion and normal css selectors are used when it makes sense.
The file package.json
contains a list of useful alias that you can invoke with npm run <alias>
.
Ionic comes with some very useful tools. Essentially it allows three differents way to test and deploy your code :
http://localhost:8100
file://
urlhttp://<ip>:8100
even though your are on a real deviceWARNING: if I’m not mistaken, ionic built-in proxy is not working behind a corporate proxy.
For myself, I’m working either on wifi or on a local network made with my mobile.
The following npm commands are wrappers around ionic and cordova commands.
Il simplifies day-to-day job but allows nonetheless to use direct ionic or cordova commands when needed.
You may pass a --lab
option as it will delegate to ionic serve
internally:
npm run serve -- --lab
npm test
) and remotely in SauceLabs (npm run test-saucelabs
) README.md
.If you simply want to build code without mocks/proxy/livereload options.
Project is built inside www
.
Just hit grunt dev
to build the whole project for development.
To reduce build time, you can use grunt
or grunt newdev
to build only what changed since last build.
grunt dist
will build with distribution options.
I almost never use low-level grunt tasks. I prefer to use npm high-level tasks. But it is good to understand how it works.
Instrument conf/dev.js
: change API endpoint and enable the livereload option in order to leverage a reverse proxy on the local machine.
Make sense when serving code via a web server :
npm run serve
npm run <platform>-lr
Instrument conf/dev.js
: enable ngCordova mocks.
Make sense when serving code in a browser :
npm run serve
Define which file from conf/
will be used as source of patterns. These patterns will be used to make replacements in source files.
See grunt-replace.
For instance: grunt dev --patterns foo
would use conf/foo.js
.
Define which platform is currently built. May be useful if you want to do something for a specific platform like adding or removing a library…
For instance: grunt dev --platform windows
We just saw the --patterns
grunt option allows to choose a set of patterns. There is two default sets: dev
& dist
.
The first way to instrument code is by using the @@
syntax. It allows to insert any variable in any file like that:
meta(http-equiv='Content-Security-Policy', content="@@csp")
As an alternative, in javascript files, I prefer to declare a constant using that variable in app.constant.js
and then inject it in my angular components.
I recommend to do it that way for javascript files in order to respect the dependency injection paradigm which is used every where in angular notably in unit tests.
// Backend endpoint
app.constant('apiEndpoint', '@@apiEndpoint');
…
// Restangular configuration
function setRestConfig(Restangular, apiEndpoint) {
…
}
There is one gruntfile.js
in the root ; then everything else grunt related is inside grunt
folder. Each file groups several grunt tasks per type.
Temporary file instrumentation is done inside .tmp
.
Target files are generated or copied to cordova www
folder. That folder is emptied before each build and should not contain versionned source code.
aliases.yaml
Define grunt aliases the easy way. There is 3 default aliases. More on than later. For novices, a grunt alias is just a task invoking a set of tasks.
grunt/css.js
SASS files are imported into _partials.scss
and then compiled into app.css. PostCSS allows us to instrument this generated file to add and remove vendor prefixes based on our scope (which browser we want to support).
grunt/script.js
Angular functions eligible to dependency injection are rewritten to use the array notation in order to be compatible with minification. Code is contactenated and eventually minified (in production mode).
grunt/template.js
Compile jade templates as either html file or javascript angular module. Either case, not jade compilation is made at runtime.
grunt/quality.js
Analyse javascript files content (semantics and style).
grunt/dist.js
Task related to the release process. Initialized with basic tools. May highly from one project to another.
grunt/doc.js
Groc might be handy when discovering code written by others. Plato is a simple solution to get some insights about code quality. Less evolved than SONAR.
grunt/serve.js
Utility grunt tasks to launch in parallel several tools. Default chokidar configuration.
Each file in
grunt/
folder may contain a chokidar task which is responsible to watch files for changes.
I prefer to put them near the related tasks because it makes more sense and is easier to debug.
grunt/test.js
Karma configuration.
grunt/assets.js
Copy static files.
grunt/common.js
Everything else like patterns replacement or cleaning.
WARNING: if I’m not mistaken, ionic built-in proxy is not working behind a corporate proxy.
To build, launch a local web server and watch for changes:
npm run serve
You may pass a --lab
option as it will delegate to ionic serve
internally:
npm run serve -- --lab
Code is loaded from http://localhost:8100
.
To build and deploy on device (or simulator if any):
npm run <platform>
Allowed platform
values:
android
ios
Code is loaded from file://assets/index.html
.
You may use instead:
android-lr
ios-lr
It will also start a watcher for code changes and the built-in ionic web server in livereload mode.
Code is loaded from http://<your ip>:8100
.
See npm scripts in package.json
and IONIC CLI to have a better understanding.
Unit tests are run by Karma and written with Jasmine.
To launch unit tests locally in a PhantomJS container:
npm run test-local
# Equivalent to `npm test`
doc/test/junit
doc/test/coverage
(used also by codecov.io)To launch unit tests locally in a Chrome container in debug mode and watch for changes:
npm run test-local-w
JUnit and coverage reports are disabled.
To launch unit tests remotely on iOS/Android emulators via the SauceLabs cloud platform:
npm run test-saucelabs
To launch all unit tests available:
npm run test-full
Error codes should be defined in err.factory.js
.
Err
is a class that inherits the standard javascript Error
class in order to add new attributes like code
.
Its message is automatically defined based on its code but it is possible to override that message. You may also give it a cause.
To create a new error, you can do:
// if 1000 is an an existing error code, its message will be found automatically.
if(condition) throw new Err(1000);
// You can also pass options like a source error or a specific hardcoded message.
throw new Err(1000, {source: err, message: 'my hardcoded message'});
// You may also set the `ui` flag to true in order to inform the default handler it can safely print that error to the user.
throw new Err(1000, {ui:true});
Angular default error handler, a.k.a $exceptionHandler
was decorated to show a native toast in last resort. If the received err is an instance of Err
and ui
is set to true
its message
attribute is used.
Concerning chains of promises, you should follow some rules:
the one ending a chain of promises is responsible in case of rejection to throw an exception if pertinent in order to trigger $exceptionHandler
.
Indeed, in $q
angular implementation, contrary to Q
there is no done
function to catch uncaught rejected promises.
the one throwing an exception should throw an instance of Err
with at least a code.
In order to buy time and code less, you might want to reuse throwErr
injectable function.
return countryLanguageService.allCountries().then(function (response) {
vm.countries = response.data;
}).catch(showErr);
////////
function showErr(err){
if( I can handle that error) do something
else throwErr(err);
}
To resume, if you do not have any error handling to do in one of your component, you should at least use throwErr
so generic errors are being handled by $exceptionHandler`.
return countryLanguageService.allCountries().then(function (response) {
vm.countries = response.data;
}).catch(throwErr); // throwErr is injectable via DI
I do not recommend to auto hide the splashscreen. Depending on your device velocity the splashscreen may hide itself before the webview is visible. This is what happens on Android:
Augmenting the delay may be a solution if you didn’t set preference SplashShowOnlyFirstTime
to false. It is not very pretty though.
I recommend to either hide the splashscreen yourself when you are ready or use a very basic native splashscreen (one color) and then a web splashscreen. A web splashscreen allows much more flexibility and creativity but is more expensive.
Warning: the plugin splashscreen is broken in version 3.0.0 and 3.1.0%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22Plugin%20Splashscreen%22%20AND%20text%20~%20%22hide%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC): you can’t hide the splashscreen yourself.
I don’t know a cordova way to customize the app’s default background, but by modifying the code of each shell and defining a custom background color in adequation with your look it can be even prettier. I don’t like modifying native code though.
I prefer not to automate more than that in order to have control after each step. But it would be easy to make an npm script.
# 1. Modify package.json and config.xml
grunt bump-only[:<patch|minor|major|…>]
# 2. Generate changelog
grunt conventionalChangelog
# 3. Commit, create tag and push to origin (including tags)
grunt commit-only
Regarding changelog generation, my conf is not standard.
I should have used angular conventions which I like a lot. At least, my commit syntax is simpler:
feature/<scope> — <subject>
for featuresbugfix/<scope> — <subject>
for bug fixesI add also a body to my commits some time by separating the header and the body with an empty line.
Almost everything is a feature in this app as it is a demo app. A normal app would use more types.
I recommend to follow solid conventions like the one used by the Angular or IONIC team which have proven to work.
Plus they are handled by the conventional-changelog plugin natively.
The conventionalChangelog
task inside grunt/dist.js
is a good example of customisation if like me your commit messages are not standard.
file://
url is used in place of cdvfile://
url)google-analytics-plugin
has been updated during summer with not so smart breaking changes in the API. Waiting for ngCordova
to be updated.