Ionic2/Angular2 wait for SQLite database to open before querying - sqlite

I'm using Ionic 2 (Angular 2) for a Hybrid app. I inject a shared provider into the page that will display data from my SQLite3 database and then proceed to load the data. However, on creation of my database provider opening the database takes some time (very little). My code (as of this moment) however does not wait for the database to be opened before querying, which obviously results in an error.
How can I structure my code that it will wait for the database to be opened in order to evade a crash?
The constructor of my database provider:
constructor(private platform: Platform) {
this.platform.ready().then(() => {
if(this.isOpen !== true) {
this.storage = new SQLite();
this.storage.openDatabase({name: "data.db", location: "default"}).then(() => {
this.isOpen = true;
this.storage.executeSql("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)", []);
});
}
});
console.log('Hello Database Provider');
This provider gets injected into the constructor of my page.
When the page (home page) is loaded it triggers an event that calls a load() function.
ionViewDidLoad() {
this.load();
console.log('Hello Home Page');
The load function:
public load() {
this.database.getPeople().then((result) => {
this.itemList = <Array<Object>> result;
}, (error) => {
console.log("LOAD ERROR: ", error);
});
I'm very much hoping someone can point me in the right direction :)

I've finally found a solution to my problem.
For starters I've added a function to my provider that checks if the database is loaded, if it isn't it proceeds to load it:
public openSQLiteDatabase() {
return new Promise((resolve, reject) => {
if(this.isOpen) {
console.log("DB IS OPEN");
resolve(this.isOpen);
}
else {
console.log("DB IS NOT OPEN");
this.platform.ready().then(() => {
this.storage.openDatabase({name: "data.db", location: "default"}).then(() => {
this.appsettings.openSQLiteDatabase().then(() => {
this.appsettings.getSettings().then((result) => {
let settings: Settings = <Settings> result;
this.selectedDataset = settings.selectedDataset;
this.isOpen = true;
resolve(this.isOpen);
});
});
}, (error) => {
reject(error);
});
});
}
});}
As this function returns a promise (JS Promises) it allows me to wait for the database to be opened before doing anything else (such as querying).
My function in the page-specific TypeScript file:
ionViewDidLoad() {
this.database.openSQLiteDatabase().then(() => {
this.loadDictionary();
});}
With code like this I never have problems with queries being performed before my database has been opened!

Related

Firebase V9 does not give error in catch when offline

I want to set state in catch even if user offline but firebase V9 setDoc does not give anything in catch when user offline
For Example: in Authentication, if the user offline firebase gives (network error) in catch but in firestore "add document" no message from catch...
This is by design thanks to Firestore's Offline Behaviour (queued up to the right spot, but I do recommend watching in full).
The promise will resolve once the server has acknowledged the request. If the server is currently unavailable, that request is cached within the SDK and attempted as soon as a connection is restored. During this window, the Promise will be kept in its pending state because that's the state its actually in - pending. While the promise may not resolve, all your local realtime listeners and such will still fire off and your app will function as normal - just offline.
Dealing with this behaviour is an exercise for the developer. One way to approach this would be to use Promise.race() to implement your own offline-handling logic.
As a quick & dirty example, here's a setDocWithTimeout implementation:
const setDocWithTimeout = (ref, data, options) => {
const timeoutMS = options && options.timeout || 10000;
const setDocPromise = setDoc(ref, data);
return Promise.race([
setDocPromise.then(() => ({ timeout: false })),
new Promise((resolve, reject) => setTimeout(resolve, timeoutMS, { timeout: true, promise: setDocPromise }));
]);
}
which you can apply using:
try {
const result = await setDocWithTimeout(doc(db, "cities", "new-city-2"), data);
if (result.timeout) {
// offline? (could be poor connection too)
console.log("Document added to Firestore queue");
// dismiss any blocking UIs/loading bubbles
// tell user will be written to server later
await result.promise; // wait for write to complete as before
}
// online! (or back online)
console.log("Document written successfully!");
} catch (err) {
console.error(`error found! ${err}`);
}
Alternatively where an error is thrown:
const setDocWithTimeoutError = (ref, data, options) => {
const timeoutMS = options && options.timeout || 10000;
const setDocPromise = setDoc(ref, data);
return Promise.race([
setDocPromise,
new Promise((_, reject) => setTimeout(reject, timeoutMS, new Error("timeout"));
]);
}
which you can apply using:
try {
await setDocWithTimeoutError(doc(db, "cities", "new-city-2"), data);
console.log("Document written successfully!");
} catch (err) {
console.error(`error found! ${err}`);
}
works on web v9, see
docs from v8.
import { onLog } from 'firebase/app';
onLog((e) => {
const { level, message } = e;
if (level === 'warn') {
console.log('connection interruption after intial load was success:', message);
}
if (level === 'error') {
console.log('no connection on inital load:', message);
}
});

How to add column to table?

Using "react-native-sqlite-storage" the application is already live on Google Play. I want to add two columns to an existing database table in such a way the application will not crash for existing users.
When you will add column to existing table you will get error like
error: {"message":"no such column: todoStar","code":5} // todoStar is column name
In error block you can alter the table and add your column. Make sure you write the code inside error block
if (error.message.indexOf('no such column: todoStar') > -1) {
dbConnection.transaction(function (tx2) {
let query = 'ALTER TABLE todos ADD todoStar Int(32);'; // todos is tableName
tx2.executeSql(
query,
'',
async (tx3) => {
console.log('new column added');
},
(error) => {
console.error(
'error: while adding column' + JSON.stringify(error),
);
},
);
});
}
As usual official documentation only contains basic information, and there is not much information on migration or database upgrades.
After hours of long research, I found that SQLite has user_version which can be used to know which version the user currently has, based on that we can execute upgrade queries.
I am using expo-sqlite, but the below solution should also work for react-native-sqlite-storage
Example:
/** database.tsx **/
export function getUserDBVersion() {
return new Promise<SQLite.SQLResultSet>((resolve, reject) => {
database.transaction((tx) => {
tx.executeSql(
`PRAGMA user_version`,
[],
(_, res) => {
resolve(res);
},
(_, error) => {
reject(error);
return true;
}
);
});
});
}
export function updateUserDBVersion(version: number) {
return new Promise<SQLite.SQLResultSet>((resolve, reject) => {
database.transaction((tx) => {
tx.executeSql(
`PRAGMA user_version = ?`,
[version],
(_, res) => {
resolve(res);
},
(_, error) => {
reject(error);
return true;
}
);
});
});
}
/** App.tsx **/
useEffect(() => {
getUserDBVersion().then((res) => {
const user_version = res.rows._array[0].user_version;
if (user_version < 1) {
//User has old version
//(1) Run function to upgrade your database tables
alterTables().then(() => {
//(2) Update user_version so that next time you can check if < 2 for new updates
updateUserVersion(1);
});
}
});
}, []);
I think this is an elegant way for 2 reasons
You can update all of your databases and tables in one place
You can display the splash screen until all this migration is completed
*For readability I have not added error handling part above
*Most of the above code will be the same for react-native-sqlite-storage, if not update your code accordingly
*The above code is in typescript, simply remove types if you are using jsx

How can I know sqlite database loading done in expo app

SQLite database is broken when executing query soon after app loading
This is Expo(react-native) app and the database is loaded from local storage.
If I wait a few seconds to use app(execute queries), the issue does not happen.
Maybe, this issue happens when a query is executed before all data loaded from local storage.
in APP.js
async function loadResourcesAsync() {
await Promise.all([
FileSystem.makeDirectoryAsync(`${FileSystem.documentDirectory}SQLite`, {
intermediates: true
}).then(
FileSystem.downloadAsync(
Asset.fromModule(require("./assets/db/mydb.db")).uri,
`${FileSystem.documentDirectory}SQLite/mydb.db`
)
)
]);
}
and in a Screen.js like this
const db = SQLite.openDatabase("mydb.db");
db.transaction(
tx => {
tx.executeSql(sql, [queryChar], (_, { rows: { _array } }) => {
// console.log(JSON.stringify(_array));
setSearchResult(_array);
});
},
e => {
console.log(e);
});
},
null
);
I expect a callback from openDatabase function, but it does not have such a callback.
How can I know a database loading done?

empty array when subscribing to service that returns Observable

I'm subscribing to a service to get an Array of Teams
teamService code:
Here my teamService code (get results from firebase):
getTeams(): Observable<Team[]> {
this.login();
let teams: Team[] = [];
return this._db.list('teams')
.map((response) => {
response.forEach(team => {
this._fb.storage().ref().child('teams/' + team.photo).getDownloadURL()
.then(imageUrl => {
teams.push(new Team(team.id, team.name, imageUrl, team.zone, team.points, team.matchesPlayed,
team.matchesWon, team.matchesDrawn, team.matchesLost, team.goalsScored,
team.goalsReceived, team.dif));
})
.catch(error => {
console.log(error);
});
});
return teams;
});
}
Here I'm subscribing to my service to get the teams:
loadStandings() {
let loading = this.loadingCtrl.create({
spinner: 'crescent'
});
loading.present();
this.teamService.getTeams().subscribe(t => {
this.teams = t;
console.log('showing this.teams');
console.log(this.teams); // this.teams declared as any[]
this.teamsOrdered = _.orderBy(this.teams, ['points', 'dif'], ['desc', 'desc']);
console.log('showing this.teamsOrdered');
console.log(this.teamsOrdered); // this.teamsOrdered declared as any[]
});
loading.dismiss();
}
console.log(this.teams) shows me the expected array returned from the service but console.log(this.teamsOrdered) is empty.
Lodash is imported import * as _ from 'lodash'; at the top of my ts file
Here screenshot from console
Edit: I added my getTeamsService code, when debugging (put a breakpoint on console.log(this.teams); I realized that the code below this.teams=t is executing before the values are retrieved from the service call.
If I don't debug then the result are in the same from my console output screenshot.
What am I doing wrong?

ionic 2 Error 'ReferenceError: sqlitePlugin is not defined'

I'm been using the ionic 2 native sqlite plugin to make a basic app to capture and store images to the phones local storage. I keep getting an uncaught exception error (seemingly from the sql plugin) sometimes when i run the app on the genymotion emulator.
This normally happens when the app reloads when i'm using the -live parameter(ionic run android -c -l).
I also noticed that data which stored on the app doesn't appear(this again indicates that there is some problem loading the stored data when 'live reloading')
I've put the error i get on the console below:
The Console error message
[13:28:27] Starting 'html'...
[13:28:27] Finished 'html' after 42 ms
HTML changed: www/build/pages/home/home.html
HTML changed: www/build/pages/map-detail-component/map-detail-component.html
HTML changed: www/build/pages/map-page/map-page.html
0 298305 log Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
1 298474 group EXCEPTION: Error: Uncaught (in promise): ReferenceError: sqlitePlugin is not defined
2 298475 error EXCEPTION: Error: Uncaught (in promise): ReferenceError: sqlitePlugin is not defined
3 298476 error STACKTRACE:
4 298477 error Error: Uncaught (in promise): ReferenceError: sqlitePlugin is not defined
at resolvePromise (http://192.168.56.1:8100/build/js/zone.js:418:31)
at resolvePromise (http://192.168.56.1:8100/build/js/zone.js:403:17)
at http://192.168.56.1:8100/build/js/zone.js:451:17
at ZoneDelegate.invokeTask (http://192.168.56.1:8100/build/js/zone.js:225:37)
at Object.NgZoneImpl.inner.inner.fork.onInvokeTask (http://192.168.56.1:8100/build/js/app.bundle.js:37360:41)
at ZoneDelegate.invokeTask (http://192.168.56.1:8100/build/js/zone.js:224:42)
at Zone.runTask (http://192.168.56.1:8100/build/js/zone.js:125:47)
at drainMicroTaskQueue (http://192.168.56.1:8100/build/js/zone.js:357:35)
at XMLHttpRequest.ZoneTask.invoke (http://192.168.56.1:8100/build/js/zone.js:297:25)
5 298478 groupEnd
6 298481 error Unhandled Promise rejection:, sqlitePlugin is not defined, ; Zone:, angular, ; Task:, Promise.then, ; Value:, [object Object], ReferenceError: sqlitePlugin is not defined
at http://192.168.56.1:8100/build/js/app.bundle.js:97420:13
at new ZoneAwarePromise (http://192.168.56.1:8100/build/js/zone.js:467:29)
at SQLite.openDatabase (http://192.168.56.1:8100/build/js/app.bundle.js:97419:16)
at new SqlService (http://192.168.56.1:8100/build/js/app.bundle.js:218:21)
at DebugAppView.Object.defineProperty.get (MyApp.template.js:18:67)
at DebugAppView._View_MyApp_Host0.injectorGetInternal (MyApp.template.js:35:79)
at DebugAppView.AppView.injectorGet (http://192.168.56.1:8100/build/js/app.bundle.js:31753:21)
at DebugAppView.injectorGet (http://192.168.56.1:8100/build/js/app.bundle.js:31945:49)
at ElementInjector.get (http://192.168.56.1:8100/build/js/app.bundle.js:31246:33)
at ElementInjector.get (http://192.168.56.1:8100/build/js/app.bundle.js:31249:48)
7 298519 log DEVICE READY FIRED AFTER, 1004, ms
8 298609 log Entering: map-page
9 298620 log ERROR: , {}
This goes away when i restart the emulator but then sometimes appears again after more usage.
I thought this could be due to the SQLite db being created before the plugin is imported but i've imported the plugin in my root app.ts file (shown below) (the whole app can be seen in this github repo)
Root app.ts
#Component({
template: '<ion-nav [root]="rootPage"></ion-nav>',
// Declare services
providers: [ SqlService ]
})
export class MyApp {
rootPage: any = MapPage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
ionicBootstrap(MyApp);
I've confined any use of the plugin to an sqlService (shown below)
SQL Service
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
import { SQLite} from 'ionic-native';
#Injectable()
export class SqlService {
private db: SQLite;
private isOpen: boolean;
public constructor() {
if(!this.isOpen){
this.db = new SQLite();
this.db.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
this.db.executeSql("CREATE TABLE IF NOT EXISTS places (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, img TEXT)", []).then((data) => {
console.log("TABLE CREATED: " + data);
}, (error) => {
console.log("Unable to execute SQL" + error);
});
this.isOpen=true;
});
}
}
public add(title: string, base64Img: string) {
return new Promise((resolve, reject) => {
this.db.executeSql("INSERT INTO places (title, img) VALUES ( '"+ title +"', '"+ base64Img +"')", [])
.then((data) => {
resolve(data);
}, (error) => {
reject(error);
});
});
}
// Remove individual Location
public remove(id: number){
return new Promise((resolve, reject) => {
this.db.executeSql("DELETE FROM places WHERE id = "+ id +";", [])
.then((data) => {
resolve(data);
}, (error) => {
reject(error);
});
});
}
public locDetail(id: number){
return new Promise((resolve, reject) => {
this.db.executeSql("SELECT * FROM places WHERE id = "+ id +";",[])
.then((data) => {
if(data.rows.length===1){
// console.log("ttitle: " + data.rows.item(0).title + " id: " + data.rows.item(0).id + " img: " + data.rows.item(0).img );
let place = [];
place.push({
id: data.rows.item(0).id,
title: data.rows.item(0).title,
img: data.rows.item(0).img
});
resolve(place);
}
}, (error) => {
reject(error);
});
});
}
// Refresh and initialise the places object
public refresh() {
return new Promise((resolve, reject) => {
this.db.executeSql("SELECT * FROM places", []).then((data) => {
let place = [];
if (data.rows.length > 0) {
for (var i = 0; i < data.rows.length; i++) {
place.push({
id: data.rows.item(i).id,
title: data.rows.item(i).title,
img: data.rows.item(i).img
});
}
}
resolve(place);
}, (error) => {
reject(error);
});
}
);
}
}
I recently had the same problem, make sure you check if the platform is ready also in your service. Import "Platform" from "ionic-angular" and execute your sqlite interaction code in the promise.then method.
I am using IOS and get the same error 'ReferenceError: sqlitePlugin is not defined'.
As the sqlite does not work when testing in Chrome browser I tried the ionic upload CLI function. Then got the same error.
I came across this issue on Github.
After using setTimeout it works in the Ionic view app on IOS:
setTimeout(function() {
let db = new SQLite();
db.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
db.executeSql("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)", {}).then((data) => {
console.log("TABLE CREATED: ", data);
self.dummy = data
}, (error) => {
console.error("Unable to execute sql", error);
self.dummy = error;
})
}, (error) => {
console.error("Unable to open database", error);
self.dummy = error;
});
}, 2000);
please run your projeect in emulator/simulator or android device/ios device.it's not work on browser.

Resources