Stencil web components + ionic native health + capacitor - ionic-framework

I am trying to use the native health kit in a stencil app. But health is always undefined.
I am using capacitor and stenciljs with native web components.
Msg: TypeError: Cannot read property 'isAvailable' of undefined
Anyone else had this issue?
import { Component, State, h } from "#stencil/core";
import { Health } from "#ionic-native/health/ngx";
#Component({
tag: "list-stats"
})
export class ListStats {
private health: Health;
componentDidLoad() {
this.health
.isAvailable()
.then((available: boolean) => {
console.log(available);
this.health
.requestAuthorization([
"distance",
"nutrition", //read and write permissions
{
read: ["steps"], //read only permission
write: ["height", "weight"] //write only permission
}
])
.then(res => console.log(res))
.catch(e => console.log(e));
})
.catch(e => console.log(e));
}
render() {
return [
<ion-label>
<p>Steps</p>
</ion-label>
];
}
}

You need to Inject the Health class since it's a singleton.
Just add a constructor method and Inject the Health class like the following.
export class ListStats {
constructor(private health: Health) { }
...
}

Related

PushNotifications stop working after installing Community/FCM plugin

I am currently using ionic capacitor PushNotifications for Firebase cloud messaging. By the help of this I can get tokens.
Now I want to send notifications to all app users so I decided to use topics.
For this, I installed community/FCM plugin.
capacitor-community/fcm
I also made suggested changes in the MainActivity.java file, no error during app build.
But after installation this FCM plugin, PushNotifications(token) stop working.
Even below code starts always retuning false.
if (isPushNotificationsAvailable) {
this.initPushNotifications();
}
Here is my MainActivity.java file:-
package io.ionic.starter;
import com.getcapacitor.community.fcm.FCMPlugin;
import android.os.Bundle;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import java.util.ArrayList;
public class MainActivity extends BridgeActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initializes the Bridge
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
// Additional plugins you've installed go here
// Ex: add(TotallyAwesomePlugin.class);
add(FCMPlugin.class);
}});
}
}
Here my fcm.service.ts File:-
import { Component, Injectable } from '#angular/core';
import { Capacitor } from '#capacitor/core';
import { Router } from '#angular/router';
import { Storage } from '#ionic/storage';
import {
ActionPerformed,
PushNotificationSchema,
PushNotifications,
Token
} from '#capacitor/push-notifications';
// import { FCM } from '#capacitor-community/fcm';
const isPushNotificationsAvailable = Capacitor.isPluginAvailable('PushNotifications');
#Injectable({
providedIn: 'root'
})
export class FcmService {
public topicName = 'project';
constructor(
private router: Router,
private storage: Storage
) { }
initPush() {
//This Plugin is not available on web
if (isPushNotificationsAvailable) {
this.initPushNotifications();
}
}
// Request permission to use push notifications
// iOS will prompt user and return if they granted permission or not
// Android will just grant without prompting
private initPushNotifications() {
//console.log('Initializing HomePage');
PushNotifications.requestPermissions().then(result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register();
} else {
// Show some error
}
});
PushNotifications.addListener('registration', (token: Token) => {
alert('Push registration success, token: ' + token.value);
//Store Devive Token in session variable
this.storage.set("device_token", token.value);
// FCM.getToken()
// .then((r) => alert(`Token ${r.token}`))
// .catch((err) => console.log(err));
// now you can subscribe to a specific topic
// FCM.subscribeTo({ topic: this.topicName })
// .then((r) => alert(`subscribed to topic`))
// .catch((err) => console.log(err));
});
PushNotifications.addListener('registrationError', (error: any) => {
//alert('Error on registration: ' + JSON.stringify(error));
});
PushNotifications.addListener(
'pushNotificationReceived',
(notification: PushNotificationSchema) => {
//alert('Push received: ' + JSON.stringify(notification));
},
);
PushNotifications.addListener(
'pushNotificationActionPerformed',
(notification: ActionPerformed) => {
// alert('Push action performed: ' + JSON.stringify(notification));
const data = notification.notification.data;
//console.log('Action performed: ' + JSON.stringify(notification.notification));
if (data.detailsId) {
this.router.navigate(['/bid-project', data.detailsId]);
}
},
);
}
//Reset all Badge count
// resetBadgeCount() {
// PushNotifications.removeAllDeliveredNotifications();
// }
// move to fcm demo
// subscribeTo() {
// PushNotifications.register()
// .then((_) => {
// FCM.subscribeTo({ topic: this.topicName })
// .then((r) => alert(`subscribed to topic ${this.topicName}`))
// .catch((err) => console.log(err));
// })
// .catch((err) => alert(JSON.stringify(err)));
// }
// unsubscribeFrom() {
// FCM.unsubscribeFrom({ topic: this.topicName })
// .then((r) => alert(`unsubscribed from topic ${this.topicName}`))
// .catch((err) => console.log(err));
// }
}
After removing community plugin token generation started working as before.
Please tell me what wrong is with my code.

Problem when I want to work on a different version in Ionic dashboard

I have a version deployed in the Ionic dashboard, and every time that I'm working on a new version and the device is connected to the Internet, it's replacing my version with the version that is deployed there. How can I work on a new version?
You can check if your device is connected to internet or not, using this network plugin provided by ionicframework.
let disconnectSubscription = this.network.onDisconnect().subscribe(() => { console.log('network was disconnected :-(');});
This metod will automatically catch if user disconnected their network and
let connectSubscription = this.network.onConnect().subscribe(() => {console.log('network connected!');});
using this method you can catch if user is connected to network.
So using those method you can show/hide some content for offline and online use.
I have created network service to catch Online and Offline status :
import { Injectable } from '#angular/core';
import { Network } from '#ionic-native/network/ngx'
import { BehaviorSubject, Observable } from 'rxjs';
import { ToastController, Platform } from '#ionic/angular';
export enum ConnectionStatus {
Online,
Offline
}
#Injectable({
providedIn: 'root'
})
export class NetworkService {
private status: BehaviorSubject<ConnectionStatus> = new BehaviorSubject(ConnectionStatus.Offline);
constructor(private network: Network, private toastController: ToastController, private plt: Platform) {
this.plt.ready().then(() => {
this.initializeNetworkEvents();
let status = this.network.type !== 'none' ? ConnectionStatus.Online : ConnectionStatus.Offline;
this.status.next(status);
});
}
public initializeNetworkEvents() {
this.network.onDisconnect().subscribe(() => {
if (this.status.getValue() === ConnectionStatus.Online) {
this.updateNetworkStatus(ConnectionStatus.Offline);
}
});
this.network.onConnect().subscribe(() => {
if (this.status.getValue() === ConnectionStatus.Offline) {
this.updateNetworkStatus(ConnectionStatus.Online);
}
});
}
private async updateNetworkStatus(status: ConnectionStatus) {
this.status.next(status);
let connection = status == ConnectionStatus.Offline ? 'Offline' : 'Online';
let toast = this.toastController.create({
message: `You are now ${connection}`,
duration: 3000,
position: 'bottom'
});
toast.then(toast => toast.present());
}
public onNetworkChange(): Observable<ConnectionStatus> {
return this.status.asObservable();
}
public getCurrentNetworkStatus(): ConnectionStatus {
return this.status.getValue();
}
}
And you can you this service in your component, for example:
import { Component, OnInit } from '#angular/core';
import { NetworkService, ConnectionStatus } from 'src/services/network.service';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.page.html',
styleUrls: ['./dashboard.page.scss'],
})
export class DashboardPage implements OnInit {
isOnline:boolean;
constructor(private network: NetworkService){}
ngOnInit() {
let status = this.network.getCurrentNetworkStatus();
(status == ConnectionStatus.Offline)? this.isOnline = false: this.isOnline = true;
console.log("Network status is ", this.isOnline);
}
}
<ion-header>
</ion-header>
<ion-content>
<ion-row *ngIf="isOnline">
For online
</ion-row>
<ion-row *ngIf="!isOnline">
For Offline
</ion-row>
</ion-content>

Create a common loader by intercepting http request (HttpInterceptors)

I am creating a common loader in ionic 3 but there is a problem because of manually using loader.dismiss()
Instead of creating a loader using loaderCtrl on very http request in ionic I'm planning to make only one loader. I am using a httpInterceptor and when the request is intercepted i created and present the loader. And i check if the event is of type HttpRequest, if yes the loader is dismissed.
This works fine when only http request is made on any page i.e the request is made it is intercepted the loader is presented later when the response is obtained the loader is dismissed.
But now if there are 2 request made on 1 page i gate the error of removeView not Found.
/loaderInterceptor.ts
#Injectable()
export class HttpLoaderInterceptor implements HttpInterceptor {
headersConfig: any;
loader: any
constructor(public loadingCtrl: LoadingController) { }
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
this.loader = this.loadingCtrl.create({
content: "Please wait",
});
this.loader.present()
return next.handle(req).pipe(tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
this.loader.dismiss();
}
},
(err: any) => {
this.loader.dismiss();
}));
}
}
The dismiss method is called twice as 2 response are obtained and the 2nd time there is no loader to be dismissed so we get an error.
Please help.
In my think, the request success before loading bar reason, so I created one service use to solve it. My source code is below:
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loaders = [];
private badLoaders = 0;
constructor(
private loadingController: LoadingController
) {
}
async startLoading() {
if (this.badLoaders > 0) {
this.badLoaders --;
} else {
await this.loadingController.create({
message: 'Loading ...',
}).then(loader => {
this.loaders.push(loader);
loader.present().then(() => {
if (this.badLoaders > 0) {
this.badLoaders --;
this.endLoading();
}
});
});
}
}
endLoading() {
let loader = this.loaders.pop();
if (loader) {
loader.dismiss();
} else {
this.badLoaders ++;
}
}
}
You can try it, use LoadingService.startLoading instead this.loadingCtrl.create and LoadingService.endLoading instead this.loader.dismiss();.

Events in Ionic v4

I'm using Ioni v4Beta and I'm traying to update the sidemenu when the user is login.
I search but the usual solution is use Events:
Ionic 3 refresh side menu after login
https://ionicframework.com/docs/api/util/Events/
But in the new version I don't find it, and I don't know how to do it
https://beta.ionicframework.com/docs/api
Thanks a lot, but I finally find how to import it:
import { Events } from '#ionic/angular';
Example on how to do it with subjects:
export const someEvent:Subject = new Subject();
export class ReceivingClass implements OnDestroy, OnInit
{
private someEventSubscription:Subscription;
public OnInit():void{
someEventSubscription = someEvent.subscribe((data) => console.log(data);
}
public onDestroy():void{
someEvent.unsubscribe();
}
}
export class SendingClass implements OnInit
{
public OnInit():void{
setTimeout(() => {
someEvent.next('hi');
}, 500);
}
}
Are you aware that Ionic v4 events will be deprecated soon?
I was also trying to update the sidemenu when a user logs in as well, so i tried using: import { Events } from '#ionic/angular';
However I got a warning referring me to this link https://angular.io/guide/observables#basic-usage-and-terms which I failed to follow because am not that familiar with observables.
After much research I found that I can still use events but I had to import them from angular's router directive.
This was my code before:
/* import was */
import { Events } from '#ionic/angular';
import { Storage } from '#ionic/storage';//ignore this import if doesn't apply to your code
/* inside the class */
constructor(
private events: Events,
private storage: Storage
) {
this.events.subscribe("updateMenu", () => {
this.storage.ready().then(() => {
this.storage.get("userLoginInfo").then((userData) => {
if (userData != null) {
console.log("User logged in.");
let user = userData.user;
console.log(user);
}
else {
console.log("No user found.");
let user = {};
}
}).catch((error)=>{
console.log(error);
});
}).catch((error)=>{
console.log(error);
});
});
}
changes i made that actually got my code working and deprecation warning gone:
/* import is now */
import { Router,RouterEvent } from '#angular/router';
import { Storage } from '#ionic/storage';//ignore this import if it does't apply to your code
Rest of code
constructor(
public router: Router,
public storage: Storage
){
this.router.events.subscribe((event: RouterEvent) => {
this.storage.ready().then(() => {
this.storage.get("userLoginInfo").then((userData) => {
if (userData != null) {
/*console.log("User logged in.");*/
let user = userData.user;
/*console.log(this.user);*/
}
else {
/*console.log("No user found.");*/
let user = {};
}
}).catch((error)=>{
console.log(error);
});
}).catch((error)=>{
console.log(error);
});
});
}
I got the idea after seeing this https://meumobi.github.io/ionic/2018/11/13/side-menu-tabs-login-page-ionic4.html. I hope my answer can be useful.
Steps to resolve the issue
import events in login page and in sidemenu view
In login page, after login success do your logic to publish the events.
for eg:
this.authService.doLogin(payload).subscribe((response) => {
if (response.status) {
this.storage.set('IS_LOGGED_IN', true);
this.events.publish('user:login');
}
}, (error) => {
console.log(error);
});
In sidemenu view, create a listener to watch the events 'user:login'
for eg:
this.menus = [];
// subscribe events
this.events.subscribe('user:login', () => {
// DO YOUR LOGIC TO SET THE SIDE MENU
this.setSidemenu();
});
// check whether the user is logged in or not
checkIsUserloggedIn() {
let isLoggedIn = false;
if (this.storage.get('IS_LOGGED_IN') == '' ||
this.storage.get('IS_LOGGED_IN') == null ||
this.storage.get('IS_LOGGED_IN') == undefined) {
isLoggedIn = false;
} else {
isLoggedIn = true;
}
return isLoggedIn;
}
// to set your sidemenus
setSidemenu() {
let isUserLoggedIn = this.checkIsUserloggedIn();
if(isUserLoggedIn) {
this.menus = ['Home', 'Aboutus', 'Contactus', 'My Profile', 'Logout'];
} else {
this.menus = ['Login', 'Home', 'Aboutus', 'Contactus'];
}
}

Angular 2 data service

I'm building an observable data service based on the following article: https://coryrylan.com/blog/angular-2-observable-data-services
In the article he used an array as an example, here I will use the user object since I'm developing the user service.
Here's what I got:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Events, SqlStorage, Storage } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
export interface DataStore {
user: Object
}
#Injectable()
export class UserService {
private baseUrl: string;
private storage: Storage;
private _user$: Subject<Object>;
private dataStore: DataStore;
constructor(
private http: Http
) {
this.baseUrl = 'http://localhost:3000';
this.storage = new Storage(SqlStorage);
this._user$ = <Subject<Object>>new Subject();
this.dataStore = {
user: { name: '' }
};
}
set user$(user: Object) {
this.storage.set('user', JSON.stringify(user));
this.dataStore.user = user;
this._user$.next(this.dataStore.user);
}
get user$() {
return this._user$.asObservable();
}
loadUser() {
return this.storage.get('user').then(
((user: string): Object => {
this.dataStore.user = JSON.parse(user);
this._user$.next(this.dataStore.user);
return this.dataStore.user;
})
);
}
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', { access_token: accessToken })
.retry(2)
.map((res: Response): any => res.json());
}
logout(): void {
this.storage.remove('user');
}
}
To authenticate I call the login() function and set the user data if everything ok.
this.userService.login(this.data.accessToken)
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
I feel it is better set the user data inside the service. I could do the following:
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', {
access_token: accessToken
})
.retry(2)
.map((res: Response): any => res.json())
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
}
But I won't be able to subscribe to the login() function in the component since it's already subscribed. How could I redirect the user if everything ok or show an alert if anything goes wrong in the component but setting the user inside the service?
In the main component I load the user data and set the rootPage:
this.userService.loadUser().then(
(user: Object) => this.rootPage = EventListComponent,
(error: Object) => this.rootPage = LoginComponent
);
I thought that calling the loadUser() function at this time I would not have to call it again, but I have to call it in all components that I need the user data:
this.user = this.userService.user$;
this.userService.loadUser();
I don't think the service is the way it should, what could I improve? Is there any better way to achieve what I want? Any example or idea?