Mobile app: Reacting to events on the device
Reacting to events on the device
To fulfill the requirements of the AppStore and to provide an acceptable user experience a maximum screen estate shall be filled by the Sunlightmap app’s main content, the map.
This necessitates to
- know available width and height
- react to changes in orientation.
An outline of the solution:
- create a provider
- install a native plugin, here: screenorientation
- subscribe to this plugin’s events
- upon change, re-calculate the available screen estate
- emit an eventthru Angular’s event mechanism
- in a related page’s controller, subscribe to this event
Prerequisites
Following instructions on ionicframework.com/…/screen-orientation/, the plugin needs tobe installed:
ionic cordova plugin add cordova-plugin-screen-orientation
npm install --save @ionic-native/screen-orientation
As usual, this plugin needs to be registered in the file src/app/app.module.ts
:
import { ScreenOrientation } from '@ionic-native/screen-orientation';
...
providers: [
...
ScreenorientationProvider,
...
]
This makes the screenorientation plugin available to the Ionic app.
The event mechanism are already included in the default Ionic installation, namely node_modules/ionic-angular/util/events.js
. It is a basic publish-subscribe pattern, organized in channels.
An Ionic provider to listen to changes in screen orientation
In Ionic “providers” are background services which are not just active in the currently visible page.
To listen to a change in screen orientation such a “provider” is the way to go:
mkdir -p src/providers/screenorientation
By placing the code in a file e.g. screenorientation.ts the import path will become import { ScreenorientationProvider } from '../../providers/screenorientation/screenorientation';
.
A minimal src/providers/screenorientation/screenorientation.ts
could look like this (key explanations follow below):
import { Injectable } from '@angular/core';
import { ScreenOrientation } from '@ionic-native/screen-orientation';
import { Platform } from 'ionic-angular';
import { Events } from 'ionic-angular';
@Injectable()
export class ScreenorientationProvider {
private type: string;
private onChange: any;
public availableWidth: number;
public availableHeigth: number;
constructor(
public so: ScreenOrientation,
private platform: Platform,
public events: Events) {
this.type = so.type;
this.onChange = so.onChange
this.normalizeOrientation(so.type);
this.setDimensions();
so.onChange().subscribe(
() => {
this.type = so.type
this.normalizeOrientation(so.type);
this.setDimensions();
this.events.publish('screenorientation:changed', this.type);
}
);
}
normalizeOrientation(soType) {
if (soType == 'landscape' || soType == 'landscape-primary' || soType == 'landscape-secondary') {
this.type = 'landscape'
} else if (soType == 'portrait' || soType == 'portrait-primary' || soType == 'portrait-secondary') {
this.type = 'portrait'
}
}
setDimensions() {
this.availableWidth = this.platform.width() - (2 * 16)
this.availableHeigth = this.platform.height() - 12 - 23 - 12 - 56 - 32
console.log("Width is %d and Height is %d.", this.availableWidth, this.availableHeigth);
}
}
At first this JavaScript (or better Typescript) file includes the classes Injectable
, ScreenOrientation
, Platform
and Events
. This “injectable class” then intializes the class variables type
, onChange
, availableWidth
and availableHeight
. The constructor initializes the objects ScreenOrientation
, Platform
and Events
and
- sets default values for the class variables
- directly
- with the help of the methods
normalizeOrientation
andsetDimensions
For the purpose of this app it is easier to work with “normalized” screen orientations as the Web API underneath the plugin return hyphenated string according to the device’s default layout (a tablet is primarily in landscape, a smartphone only secondarily in landscape – more about this at developer.mozilla.org/…/lockOrientation).
The constructor also subscribes to the onChange event of the screenorienation plugin and itself publishes an event on the implicitly created screenorientation:changed
channel (the colon is just a freely chosen convention here).
@Injectable()
export class ScreenorientationProvider {
//member variables
constructor(
//init
) {
//defaults
so.onChange().subscribe(
() => {
this.type = so.type
this.normalizeOrientation(so.type);
this.setDimensions();
this.events.publish('screenorientation:changed', this.type);
}
);
}
The member variable this.type
is then filled with the normalized orienatation, to make life easier down the road and not having to string-parse.
normalizeOrientation(soType) {
if (soType == 'landscape' || soType == 'landscape-primary' || soType == 'landscape-secondary') {
this.type = 'landscape'
} else if (soType == 'portrait' || soType == 'portrait-primary' || soType == 'portrait-secondary') {
this.type = 'portrait'
}
}
And finally the other member variables are filled with values from the Platform object (which was not really mentioned so far but is equally crucial to this “provider”:
setDimensions() {
//app-specific: left and right margin
this.availableWidth = this.platform.width() - (2 * 16)
//app-specific: margins, header, footer
this.availableHeigth = this.platform.height() - 12 - 23 - 12 - 56 - 32
console.log("Width is %d and Height is %d.", this.availableWidth, this.availableHeigth);
}
A sample subscriber to the re-published event
To import the custom provider from a file e.g. src/pages/map/map.ts
a relative path to the file and the name of the exported class (from export class ScreenorientationProvider {
) is used.
The page then initializes the object in its constructor, and subscribes to the changes.
///other imports
import { ScreenorientationProvider } from '../../providers/screenorientation/screenorientation';
export class MapPage {
//class variables
constructor(
//other init objects
public so: ScreenorientationProvider
) {
events.subscribe('screenorientation:changed', (type) => {
console.log('so changed: ', type);
this.reloadMap();
});
} //end of the constructor
//other (mandatory) methods
private reloadMap() {
//Getting the array of user-owned places from storage
let that = this;
this.asyncLocalStorage.getItem("places").then(function (value) {
//console.log("async got", value);
that.places = JSON.parse(value);
});
//do something with the new values
}
}
That’s it! This is how to leverage Cordova in an Ionic hybrid app with a background “provider” and event-based communication within the JavaScript running in a Web Container.