Last month I wrote a tutorial on using Ionic Native and the Device Motion plugin (Working with Ionic Native - Shake, Rattle, and Roll). In that post I detailed how to use the device's accelerometer to recognize a "shake" gesture and then reload data from a service. A reader (on the Ionic blog version of my article) had a great question:
Thats really useful and it works :-) Can anyone suggest how to implement subscription.unsubscribe(); when the page is navigated away from and then restarted when the user returns to this page?
My demo was a one page app which isn't very practical, but kept things simple for the demo. However, as soon as you add a new page to the app, you may (or may not!) notice something bad about my code - it continues to listen to the accelerometer after you've left the page. That's going to drain the device battery and make the user angry. You wouldn't like the user when they're angry - trust me.
I began by modifying my previous demo such that the list of cats actually linked to a detail page. In case you don't remember, this is how the list looked:
So I simply created a new page (don't forget, the Ionic CLI has a cool "generate" feature to make that easy!) and then linked my cats to the detail. So first I added a click event to my list item:
<ion-item *ngFor="let cat of cats" (click)="loadCat(cat)"> {{ cat.name }} </ion-item>
And then added a handler for it:
loadCat(cat) {
this.navController.push(DetailPage, {cat:cat});
}
Ok, so how do we fix our code so we only listen to the accelerometer when the view is visible? Easy - we use a view event! The Ionic docs do not do a good job of making it easy to find them, but if you look the API docs for NavController, you'll find a list of view-related events you can listen to. For my demo, I just needed ionViewWillEnter
and ionViewWillLeave
. So I simply moved my "listen for device motion" code out of the constructor and into the enter event. Here's the complete home.ts code:
import {Component} from '@angular/core';
import {NavController,Platform} from 'ionic-angular';
import {CatProvider} from '../../providers/cat-provider/cat-provider';
import {DeviceMotion} from 'ionic-native';
import {DetailPage} from '../detail/detail';
@Component({
providers: [CatProvider],
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
public cats:Array<Object>;
private lastX:number;
private lastY:number;
private lastZ:number;
private moveCounter:number = 0;
private subscription:any;
constructor(public catProvider:CatProvider, private navController: NavController, public platform:Platform) {
this.loadCats();
}
loadMore() {
console.log('load more cats');
this.loadCats();
}
loadCats() {
this.catProvider.load().then(result => {
this.cats = result;
});
}
loadCat(cat) {
this.navController.push(DetailPage, {cat:cat});
}
ionViewWillEnter() {
console.log('view will enter');
this.platform.ready().then(() => {
this.subscription = DeviceMotion.watchAcceleration({frequency:200}).subscribe(acc => {
console.log(acc);
if(!this.lastX) {
this.lastX = acc.x;
this.lastY = acc.y;
this.lastZ = acc.z;
return;
}
let deltaX:number, deltaY:number, deltaZ:number;
deltaX = Math.abs(acc.x-this.lastX);
deltaY = Math.abs(acc.y-this.lastY);
deltaZ = Math.abs(acc.z-this.lastZ);
if(deltaX + deltaY + deltaZ > 3) {
this.moveCounter++;
} else {
this.moveCounter = Math.max(0, --this.moveCounter);
}
if(this.moveCounter > 2) {
console.log('SHAKE');
this.loadCats();
this.moveCounter=0;
}
this.lastX = acc.x;
this.lastY = acc.y;
this.lastZ = acc.z;
});
});
}
ionViewWillLeave() {
console.log('view will leave');
this.subscription.unsubscribe();
}
}
So ionViewWillEnter
simply has the code I used before. No real difference there - but do note I'm storing subscription
globally to the component. That let's me then use it in ionViewWillLeave
to handle unsubscribing from the accelerometer.
I created a new folder for this version in my Cordova Demos repository - you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicnative_shake_2