Ionic Native contacts spent a lot of time and blocks UI - reactjs

I am developing with Ionic + Capacitor + React and I need to read all contacts numbers.
Using ionic-native/contacts in this way:
import { Contacts, ContactFindOptions } from '#ionic-native/contacts';
...
loadContacts() => {
this.setState(
{
loading: true,
},
() => {
var contacts = new Contacts();
const desiderFields = ['phoneNumbers'];
var options = new ContactFindOptions();
options.filter = '';
options.multiple = true;
options.hasPhoneNumber = true;
contacts.find(desiderFields, options).then((result) => { ... } );
});
}
componentDidMount() {
this.loadContacts();
}
But as soon as loadContacts start reading contacts the UI blocks and not even the state has time to render to show the loader.
Plus, the it is very slow importing contacts, showing repeatedly Fetching in bulk 250 contacts!.
I know that cordova-plugin-contacts is deprecated but I can't believe that those not exists valide alternatives to this fundamental feature.
Can't find solution.

I found this cordova plug-in that saved my life:
https://github.com/dbaq/cordova-plugin-contacts-phone-numbers

Related

How to In App Purchases with ReactJS and Capacitor

I am using Capacitor to generate both the IOS and Android apps (not using Iconic) - this works well, but we are trying to implement IAP (for IOS only at this stage) and cannot figure it out.
I have followed various guides (https://ionicframework.com/docs/native/in-app-purchase-2 and https://purchase.cordova.fovea.cc/ and https://capacitorjs.com/docs/guides/in-app-purchases) but simply cannot get it working with React (not React Native)
Can someone point me in the right direction, or provide sample code?
You didn't describe what is going wrong, but here's a basic configuration that works for me on iOS.
I'm only including the part about the store:
index.tsx
import { IAPProduct, InAppPurchase2 } from '#ionic-native/in-app-purchase-2';
const startStoreEventListeners = () => {
if (isPlatformMobile()) {
document.addEventListener(
'deviceready',
() => {
const store = InAppPurchase2;
// Needed to use IAP + cordova plugins.
// Set debug messages.
// Default.
store.verbosity = store.QUIET;
// store.verbosity = store.DEBUG;
store.register([
{
id: subMonthly,
type: store.PAID_SUBSCRIPTION,
},
{
id: subAnnual,
type: store.PAID_SUBSCRIPTION,
},
]);
// Upon approval, verify the receipt.
store.when(subMonthly).approved((product: IAPProduct) => {
product.verify();
});
store.when(subAnnual).approved((product: IAPProduct) => {
product.verify();
});
// Upon receipt validation, mark the subscription as owned.
store.when(subMonthly).verified((product: IAPProduct) => {
product.finish();
});
store.when(subAnnual).verified((product: IAPProduct) => {
product.finish();
});
// Track all store errors
store.error((err: Error) => {
debugLog('Store Error', JSON.stringify(err));
});
// https://billing-dashboard.fovea.cc/setup/cordova
store.validator =
'https://validator.fovea.cc/v1/validate?appName=secret';
store.refresh();
startIonic();
},
false,
);
} else {
startIonic();
}
};
startStoreEventListeners();
serviceWorker.unregister();
Note that #ionic-native packages are deprecated and need to be converted.

How can I utilise ApplicationInsights-JS in a service worker?

I am currently using ApplicationInsights-JS in my progressive web app. It works in my react components as I can import what I need from the relevant npm packages.
In my service worker however, I can only import logic using importScripts.
I did manage to find a CDN for ApplicationInsights-JS on their Github page however it seems that in order to initialise app insights using this library you need to have access to window in order to store the appinsights, which you cannot do from a service worker.
I tried to use the web snippet approach since the CDN seemed to be
related to that particular library, but I can't use window and am not sure how else to implement this solution.
This is a copy paste of the suggested snippet to init the app insights object from: https://github.com/Microsoft/ApplicationInsights-JS
importScripts('https://az416426.vo.msecnd.net/beta/ai.2.min.js');
const sdkInstance = 'appInsightsSDK';
window[sdkInstance] = 'appInsights';
const aiName = window[sdkInstance];
const aisdk =
window[aiName] ||
(function(e) {
function n(e) {
i[e] = function() {
const n = arguments;
i.queue.push(function() {
i[e](...n);
});
};
}
let i = { config: e };
i.initialize = !0;
const a = document;
const t = window;
setTimeout(function() {
const n = a.createElement('script');
(n.src = e.url || 'https://az416426.vo.msecnd.net/next/ai.2.min.js'),
a.getElementsByTagName('script')[0].parentNode.appendChild(n);
});
try {
i.cookie = a.cookie;
} catch (e) {}
(i.queue = []), (i.version = 2);
for (
const r = [
'Event',
'PageView',
'Exception',
'Trace',
'DependencyData',
'Metric',
'PageViewPerformance'
];
r.length;
)
n(`track${r.pop()}`);
n('startTrackPage'), n('stopTrackPage');
const o = `Track${r[0]}`;
if (
(n(`start${o}`),
n(`stop${o}`),
!(
!0 === e.disableExceptionTracking ||
(e.extensionConfig &&
e.extensionConfig.ApplicationInsightsAnalytics &&
!0 ===
e.extensionConfig.ApplicationInsightsAnalytics
.disableExceptionTracking)
))
) {
n(`_${(r = 'onerror')}`);
const s = t[r];
(t[r] = function(e, n, a, t, o) {
const c = s && s(e, n, a, t, o);
return (
!0 !== c &&
i[`_${r}`]({
message: e,
url: n,
lineNumber: a,
columnNumber: t,
error: o
}),
c
);
}),
(e.autoExceptionInstrumented = !0);
}
return i;
})({ instrumentationKey: 'xxx-xxx-xxx-xxx-xxx' });
(window[aiName] = aisdk),
aisdk.queue && aisdk.queue.length === 0 && aisdk.trackPageView({});
I get window is not defined which is expected, but I'm not sure how else I can make use of this library from the service worker.
Has anyone else had a similar implementation in which they successfully logged telemetry using ApplicationInsights from a service worker?
I realised that I was over complicating this.
Since I only needed to track a custom event, and didn't need all the automated page tracking etc that appInsights does, I ended up doing a fetch from my service worker.
I just copied the header and body format from the requests that I made using my react pages.
The below successfully logged telemetry to my app insights dashboard:
fetch(url, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify([
{
time: '2019-05-02T15:56:37.589Z',
iKey: 'INSTRUMENTATION_KEY',
name:
'Microsoft.ApplicationInsights.INSTRUMENTATION_KEY.Event',
tags: {
'ai.user.id': 'l6Tey',
'ai.session.id': 'TL+Ry',
'ai.device.id': 'browser',
'ai.device.type': 'Browser',
'ai.operation.id': 'HUfNE',
SampleRate: '100',
// eslint-disable-next-line no-script-url
'ai.internal.sdkVersion': 'javascript:2.0.0-rc4'
},
data: {
baseType: 'EventData',
baseData: {
ver: 2,
name: 'Testing manual event',
properties: {},
measurements: {}
}
}
}
])
})
.then(json)
.then(function(data) {
})
.catch(function(error) {
});
I've almost managed to use Microsoft Application Insights in our app's service worker.
The key parts are:
Using the lightweight version of appInsights (see this small remark at 4th step) with importScripts('https://az416426.vo.msecnd.net/next/aib.2.min.js').
Initialize an appInsights object:
appInsights = new Microsoft.AppInsights.AppInsights({ instrumentationKey: "[replace with your own key]" });
when track needed (during onpush event or onnotificationclick), go for appInsight.track({ eventItemFields }) then appInsights.flush().
I've said "almost" because the flush part seems to not working, I've got: "Sender was not initialized" internal error after enabling debugging.
I will publish here a working sample code if I successfully manage this issue.
References:
https://github.com/Azure-Samples/applicationinsights-web-sample1/blob/master/testlightsku.html
This response to the question: How to add analytics for Push notifications.
Using the Web SDK in a service worker is troublesome. The full version depends on a window object, while the basic SDK depends on Beacon or XmlHttpRequest for sending the messages (in file https://github.com/microsoft/ApplicationInsights-JS/blob/master/channels/applicationinsights-channel-js/src/Sender.ts):
if (!_self._senderConfig.isBeaconApiDisabled() && Util.IsBeaconApiSupported()) {
_self._sender = _beaconSender;
} else {
if (typeof XMLHttpRequest !== undefined) {
const xhr:any = getGlobalInst("XMLHttpRequest");
if(xhr) {
const testXhr = new xhr();
if ("withCredentials" in testXhr) {
_self._sender = _xhrSender;
_self._XMLHttpRequestSupported = true;
} else if (typeof XDomainRequest !== undefined) {
_self._sender = _xdrSender; // IE 8 and 9
}
}
}
}
At the moment Application Insights SDK does not seem to support service workers. Rajars solution seems to be the best option for now.
Update: There is an issue in the Github Repo about this: https://github.com/microsoft/ApplicationInsights-JS/issues/1436
A suggestion that works is by using the basic/lightweight version of Application Insights (as mentioned by Rajar) and adding a XMLHttpRequest polyfill (that uses the fetch api) before inititializing Application Insights. After that you can use the lightweight version.
An example can be found here: https://github.com/Pkiri/pwa-ai
I was trying to use AppInsightsSDK in E2E tests environment (pupeteer) and when I tried to log event or metric I got with "Sender was not initialized" error.
As #Pkiri mentioned one would need XMLHttpRequest polyfill to solve the issue. Although my scenario is not directly related to Service worker I wanted to mention that #Pkiri answer is not entirely true, because one can also use globalThis, self, window or global to get the same result according to SDK source code function getGlobalInst("XMLHttpRequest"); resolves to
function getGlobal() {
if (typeof globalThis !== strShimUndefined && globalThis) {
return globalThis;
}
if (typeof self !== strShimUndefined && self) {
return self;
}
if (typeof window !== strShimUndefined && window) {
return window;
}
if (typeof global !== strShimUndefined && global) {
return global;
}
return null;
}
And for my scenario this was a valid solution
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'AppInsights_InstrumentationKey',
},
});
global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
global.appInsights = appInsights.loadAppInsights();

Displaying array-responses in the form of a Nested menus using React

I'm fairly new to React and stuck regarding a very minor problem. I wrote an UI that essentially calls a service that returns the responses in the form of an array. Now, I need those responses from the service to be displayed in the form of a nested menu. For e. g., one of my axios calls returns a response of [1,2,3,4] and the other axios call returns [1.1,1.2,1.3,..]. I want these responses to be aligned in the form of
1
1.1
1.2
1.3
2
2.1
etc.,
i. e. the UI should show 1,2,3,4 .. and when the user clicks on 1, then 1.1,1.2 etc. should be displayed.
I'm using React, material-ui's components and redux for this.
I have a function to do the above mentioned.. but I'm not sure if I'm doing it right.
handleMenuData() {
var applist = this.props.menuData;
var appNames = [];
var moduleNames = [];
applist.forEach(app => {
app.moduleNames.forEach(module => {
try {
return axios.get(
'service url' + app.name + '/' + module,
);
} catch (error) {
console.error(error);
}
});
appNames.push({
name: app.name,
moduleNames: moduleNames,
});
moduleNames = [];
});
this.setState({
appNames: appNames,
});
}
and in my state,
this.state = {
appList: [],
appNames: [],
moduleNames: [],
};
app names are 1,2,3 and module names are 1.1,1.2 and I was thinking of using ListItemText component from material UI.
I think what you are doing is incorrect. axios.get is an asynchronous function and you are not waiting for the response to come from the server. That's why you get all arrays as empty. Try calling a recursive function like this.
const getModuleNames = (i) => {
axios.get('service url' + applist[i].name + '/' + module)
.then((response) => {
if(i < applist.length){
applist[i].moduleNames = response.data;
appNames.push({
name: applist[i].name,
moduleNames: applist[i].moduleNames
});
getModuleNames(i++);
}
else {
// code you want to execute after adding data to appNames
}
}).catch((err) => {
// error handling
});
}
getModuleNames(0);

Event listener for window.stripe

I have a Stripe integration in a React web front-end. My payment component attempts to run…
componentDidLoad() {
if ( window.hasOwnProperty('Stripe') ) {
this.setState({stripe: window.stripe(config.stripeKey)})
}
}
This sporadically fails as sometimes the React app will boot and get the component loaded before window.stripe exists. I’m currently resolving this with…
componentDidLoad() {
this.watchStripe = setInterval( () => {
if (!props.stripe && isBrowser && window.Stripe) {
this.setState({stripe: window.stripe(config.stripeKey)})
clearInterval(this.watchStripe)
}
}, 100)
}
Is there a more elegant solution to this, something that doesn't require an interval timer?
If you read the Advanced integrations section they suggest doing
componentDidMount() {
if (window.Stripe) {
this.setState({stripe: window.Stripe('pk_test_12345')});
} else {
document.querySelector('#stripe-js').addEventListener('load', () => {
// Create Stripe instance once Stripe.js loads
this.setState({stripe: window.Stripe('pk_test_12345')});
});
}
}
This adds an event listener for the load event on the script element that loads Stripe and sets the state when it is available (so no continuous polling with setInterval,setTimeout is needed)

Create a favorite list in Ionic Framework

I am very new to Ionic Framework. I am learning the framework and have tried to build a simple android app, which displays a simple list using json. Now, I want add a favorite list which will show user selected items in it. When user clicks on a button it should add that item in a favorite list. And When user click on Favorite tab it should show list of all favorite items.
At present I am trying to do this with simple json and global controller. But I am afraid if this is used on android app on a phone it will not store all favorites, it would remove all favourites once app is closed. Can anyone please suggest a better approach towards it.
Many thanks in advance.
I see you tagged the question with local storage, so why not use that? Also, you could use one of the popular mBaaS solutions like Firebase or gunDB.
As for the logic, it's quite easy: you create a new array which you use for storing these favorites (you handle the adding/removing on the favorite button click). You then use the ng-repeat on the Favorites tab to list the favorites array.
The best way to do this would be pouchdb, i m using in same way.!
Install pouchdb using command:
bower install pouchdb
add below line in index.html
<script src="lib/pouchdb/dist/pouchdb.min.js"></script>
make a service:
.factory('FavService', function (UserService) {
var FavService = {};
var localDB;
var user = UserService.getUser();
if (user) {
localDB = new PouchDB('u_' + user.id);
}
FavService.configDbs = function () {
//console.log('config dbs');
var user = UserService.getUser();
if (user) {
localDB = new PouchDB('u_' + user.id);
}
};
FavService.storeToLocal = function (product) { //change function name
if (localDB && product !== "") {
localDB.post(product);
// console.log("Action completed");
} else {
// console.log("Action not completed");
}
};
FavService.getLocalList = function (callback) {
if (localDB) {
localDB.allDocs({
include_docs: true
}).then(function (response) {
// console.log("response :"+JSON.stringify(response));
localDB = response.rows;
callback(response.rows);
}).catch(function () {
callback(null);
});
} else {
FavService.configDbs();
}
};
});

Resources