I am using Ionic to build iOS and Android apps. I want to store the user record after the first time a user logs in so that they don't have to log in again after they close the app. So, when the app starts-up it checks if they are "Logged in" by if there is a user record in the table. If not logged in (first time they run the app or later click "log out") then it should proceed to the login page.
In app.component.ts
this.platform.ready().then(() => {
this.registerBackButton();
this.statusBar.styleDefault();
this.sqlStorageService.initializeDatabase().then(()=> {
this.sqlStorageService.isLoggedIn().then(loggedIn => {
this.loader.dismiss();
this.splashScreen.hide();
if (loggedIn){
this.rootPage = HomePage;
}else{
this.rootPage = LoginPage;
}
});
});
In sqlStorageService:
initializeDatabase()
{
return this.sqlite.create({name: 'data.db', location: 'default' }).then(db => {
this.db = db;
return this.db.executeSql('CREATE TABLE IF NOT EXISTS CurrentUser(userName text primary key, firstName text, lastName text, bearerToken text, claims text)', [])
.then(data => {console.log("initialize database complete")})
})
}
isLoggedIn():Promise<boolean>{
return this.get().then(data => {
return data != null;
})
}
get() : Promise<IAppUserAuthorization> | null{
return this.db.executeSql('SELECT userName, firstName, lastName, bearerToken, claims FROM CurrentUser', [])
.then(data => {
if (data.rows.length == 0){
return null;
}
let user: IAppUserAuthorization =
{
userName: data.rows.item(0).userName,
firstName: data.rows.item(0).firstName,
lastName: data.rows.item(0).lastName,
bearerToken: data.rows.item(0).bearerToken,
isAuthenticated: data.rows.item(0).isAuthenticated,
claims: JSON.parse(data.rows.item(0).claims)
}
return user;
})
}
After the user is authenticated
return this.db.executeSql('insert or replace into CurrentUser(userName, firstName, lastName, bearerToken, claims) values(?, ?, ? , ?, ?)',
[appUser.userName, appUser.firstName, appUser.lastName, appUser.bearerToken, JSON.stringify(appUser.claims)])
.then(data => {
return JSON.parse(data.rows.item(0).value);
});
This all works fine when I deploy to an android device. I log in the first time. Then close the app... then reopen the app and I am taken to the home page.
When I deploy to any iOS device I log in the first time. then close the app.. then reopen the app and I am taken to the login page.
I want to point out that after I log in on iOS device and I am authenticated. The record is stored in the database and I can access data in the table from any other part of the code and it works as expected. It only doesn't work when I close the app and reopen it. It seems to not find the database (or not find the data in that table)
I have been banging my head from this for 3 days now. I'm sure its something really subtle I'm missing but I don't get why it works flawlessly for android and not iOS
Related
I have developed an android app in react-native and expo. I have also published the app on google play.
Now, I have made some modifications on my SQLite DB tables locally.
Suppose, before the schema of a table was like this:
CREATE TABLE expenditures (id integer primary key, max_amount REAL not null);
And now I would like to change it to this:
CREATE TABLE expenditures (id integer primary key, max_amount TEXT not null);
Is there any way to run a method after a new update/upgrade on a production app (google play store)? That way I can alter the tables only once after the upgrade, and other newly installed users won't be affected by this function. I found two methods on native android:
onCreate: Called for the first time when creation of tables are needed.
onUpgrade: This method is called when database version is upgraded.
But since I have developed my app with react-native and expo, I can't use the above methods. Although I have found onUpgrade in the expo code, I am not sure how to use this feature in expo.
Or is there any better way to handle database migrations on a published app in react-native and expo?
I don't think you can really use the versioning stuff you linked to, as that will drop your db and recreate it from scratch, so you would lose your data.
A simple solution to this is to manually keep track of migrations you've already executed in a table. Then you can create this table if it doesn't exist yet (which can be done in a very dumb way by first trying to query it, and if that fails, create it). If you have a list of all known migrations in order, you can just drop items that already have an entry in the table and run the remaining ones.
From an old Cordova application I wrote this code (yeah it's really old, it's still using Require JS to define the module):
/**
* Provide access to an SQL database, using the SQLite plugin for
* Cordova devices so we aren't limited in how much data we can store,
* and falling back to browser native support on desktop.
*
* Unfortunately webSQL is deprecated and slowly being phased out.
*/
define(['require', 'module', 'deviceReady!'], function(require, module, isCordova) {
'use strict';
var dbRootObject = isCordova ? window.sqlitePlugin : window,
config = module.config();
if (typeof dbRootObject.openDatabase == 'undefined') {
window.alert('Your browser has no SQL support! Please try a Webkit-based browser');
return null;
} else {
var db = dbRootObject.openDatabase(config.dbName, '', 'Direct Result database', null),
transaction = function(callback) {
// We go through this trouble to automatically provide
// error reporting and auto-rollback.
var makeFacade = function(t) {
return {
sql: function(sql, args, okCallback, errorCallback) {
var okFn, errFn;
if (okCallback) {
okFn = function(t, r) { return okCallback(makeFacade(t), r); };
} else {
okFn = null;
}
if (errorCallback) {
errFn = function(t, e) { console.log('SQL error: '+sql, e); return errorCallback(makeFacade(t), e); };
} else {
errFn = function(t, e) {
// It's important we throw an exn,
// else the txn won't be aborted!
window.alert(e.message + ' sql: '+sql);
throw(e.message + ' sql: '+sql);
};
}
return t.executeSql(sql, args, okFn, errFn);
}
};
};
return db.transaction(function(t) {
return callback(makeFacade(t));
}, function(e) { console.log('error'); console.log(e); });
},
// We're going to have to create or own migrations, because
// both the Cordova SQLite plugin and the Firefox WebSQL
// extension don't implement versioning in their WebSQL API.
migrate = function(version, upFn, done, txn) { // "Down" migrations are currently not supported
var doIt = function(t) {
t.sql('SELECT NOT EXISTS (SELECT version FROM sqldb_migrations WHERE version = ?) AS missing',
[version], function(t, r) {
if (r.rows.item(0).missing == '1') {
upFn(t, function() {
t.sql('INSERT INTO sqldb_migrations (version)'+
'VALUES (?)', [version], done);
});
} else {
done(t);
}
});
};
if (txn) doIt(txn);
else transaction(doIt);
},
maybeRunMigrations = function(callback) {
var migrations = [],
addMigration = function(name, migration) {
migrations.push([name, migration]);
},
runMigrations = function(t) {
if (migrations.length === 0) {
callback(t);
} else {
var m = migrations.shift(),
name = m[0],
migration = m[1];
migrate(name, migration, runMigrations, t);
}
};
// ADD MIGRATIONS HERE. The idea is you can just add migrations
// in a queue and they'll be run in sequence.
// Here are two example migrations
addMigration('1', function (t, done) {
t.sql('CREATE TABLE people ('+
' id integer PRIMARY KEY NOT NULL, '+
' initials text NOT NULL, '+
' first_name text NOT NULL, '+
' family_name text NOT NULL, '+
' email text NOT NULL, ', [], done);
});
addMigration('2', function(t, done) {
t.sql('ALTER TABLE people ADD COLUMN phone_number text', [], done);
});
transaction(function(t) {
t.sql('CREATE TABLE IF NOT EXISTS sqldb_migrations ('+
' version int UNIQUE, '+
' timestamp_applied text NOT NULL DEFAULT CURRENT_TIMESTAMP '+
')', [], function (t, r) { runMigrations(t, migrations); });
});
};
// Expose "migrate" just in case
return {transaction: transaction, migrate: migrate, maybeRunMigrations: maybeRunMigrations};
}
});
You'll also need to take a lot of care, as I found out the hard way you cannot actually alter or even drop columns with SQLite (or at least not with the Cordova plugin at the time I wrote this code)! So also be very careful with constraints or you'll end up painting yourself into a corner.
I have not tried it, but it might be possible if you rename the old table, create the new one again with the changed columns and then copy over the data.
you can put the sql alteration files in assets folder android/app/src/main/assets/ like
<version>.sql -> 1.sql or 2.sql
and these file can contain the migration query like
alter table NOTE add NAME TEXT;
and trigger these query according to version of app in onUpgrade() method
I have issue with querying the records from SQL Lite DB using cordova in Android
this.platform.ready().then(() => {
this.sqlite.create({
name: 'temp.db',
location: 'default'
}).then((db: SQLiteObject) => {
console.log('Querying for temp user '+user.userName+'Password '+user.password);
console.log('User queried'+user.userName);
db.executeSql("SELECT * FROM USER where USER_NAME = ? and USER_PWD=?", [user.userName,password]).then(
response => {
let records='';
for (let i = 0; i < response.rows.length; i++) {
records = records+ JSON.stringify(response.rows.item(i))+'\n'; //Prints row correctly
}
this._util.presentAlert('Records selected like from- USR-',records);
})
.catch(
e => this._util.presentAlert('Fail- Select like from- USER-Temp DBUSER',e));
db.executeSql("SELECT * FROM USER where USER_NAME = ? and USER_PWD=? ", [user.userName,password ]).then(
response => {
if (response && response.rows && response.rows.length > 0) {
for (let i = 0; i < response.rows.length; i++) {
let access = {
firstName :response.rows.item[i].FIRST_NAME, //This is undefined.
lastName :response.rows.item[i].LAST_NAME,
userName:response.rows.item[i].USER_NAME,
userId:response.rows.item[i].USER_ID
}
observer.next(access);
}
observer.complete();
} else {
let access = {status:'Fail',msg:'Bad credentials for Temp DB login'};
console.log('No record for the user from- USER'+user.userName);
observer.next(access);
observer.complete();
}
})
.catch(
e => {
console.log('Fail- Select query gone wrong * from- USER FOR Temp DB LOGIN' + e);
let access = {status:'Fail',msg:'Bad credentials for Temp DB login'};
observer.next(access);
observer.complete();
});
The issue is this one is printing the records correctly
JSON.stringify(response.rows.item(i))
O/P
{'USER_ID':1,'FIRST_NAME':'Temp','LAST_NAME':'User','USER_NAME':'TEMPUSER','USER_PWD':'TEMPPWD'}
Below is throwing undefined error
firstName :response.rows.item[i].FIRST_NAME
Fail- Select query gone wrong * from- USER FOR Temp DB LOGIN TypeError: Cannot read property 'FIRST_NAME' of undefined
Why am I unable to fetch it as as JSON?
This is how I resolved the issue. Incase if it helps anyone
1.Removed cordova sqllite plugin
2.Reinstalled Ionic sqllite plugin -latest
3.Took npm update of cordova
4.Performed cordova prepare android
5.Executed ionic build android
6.Corrected the typo from the queries response.rows.item(i).FIRST_NAME. It should be curly brace only. Sadly IDE doesnt trigger any error
I donĀ“t know how late it is, but you must use:
response.rows.item(i).FIRST_NAME
() instead of [].
I have an app that uses WebSQL transactions to create a table, insert data, and retrieve information from a database.
The app works fine on the Ripple Nexus (Galaxy) but not on the device (Galaxy Note 5) nor the emulator. The UI is there: text boxes, labels, buttons, etc. However, according to my notice, the operations on the database are not performed.
What can I do?
There are not enough details here to tell what is going on and no code samples. Note that ripple is using http and your app on the device is probably using a non http:// calling location. Another possible issue is that on your machine there is web connectivity to a local http endpoint and on the device the end point would not be available unless it is public on the internet / available to the device.
Here is sample of my code, open database and populate data tables:
document.addEventListener('deviceready', onDeviceReady.bind(this), false);
var db = openDatabase('DUTIESDB', '1.0', 'Test DB', 2 * 1024 * 1024);
db.transaction(function (tx) {
tx.executeSql("DROP TABLE DUTIES", []);
tx.executeSql("DROP TABLE SDUTIES", []);
tx.executeSql('CREATE TABLE IF NOT EXISTS DUTIES (id, name, date, time, course, room)');
tx.executeSql('CREATE TABLE IF NOT EXISTS SDUTIES (id, name, date, time, course, room, invg)');
});
PupulateSupTable();
function PopulateSupTable() {
var xmlhttp = new XMLHttpRequest();
var url = "/scripts/SupDuties.txt";
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var myArr = JSON.parse(xmlhttp.responseText);
db.transaction(
function (tx) {
var k, sql = 'INSERT INTO SDUTIES (id, name, date, time, course, room, invg) VALUES (?,?,?,?,?,?,?)';
for (k = 0; k < myArr.length; k++) {
tx.executeSql(sql, [myArr[k].ID, myArr[k].Name, myArr[k].Date, myArr[k].Time, myArr[k].Course, myArr[k].Room, myArr[k].Invigilator]);
}
}
);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
I want to know how can I achieve the following SQL query on parse,
e.g. assume we have 2 tables/classes in our DB: User, Profile with example values in brackets.
User
- email(tt#tt.com)
- Name(tt)
Profile
-email(tt#tt.com)
-age(23)
Sql query,
select User.email,User.name,Profile.age from User
JOIN Profile on User.email = Profile.email
Where User.email = 'tt#tt.com'
The resulting recordset would be "tt#tt.com,tt,23e".
Now, if I want to make the same thing with Parse, or better, how can I achieve this??
I read about these structures but I don't know if they apply to this case and how to use them. I'm developing in Android
any help would be greatly appreciated.
If you have pointers to _Users on your Class Profile best method would be this one :
var Profile = Parse.Object.extend("Profile");
var query = new Parse.Query(Profile);
query.equalTo("email", request.params.email);
return query.first().then(null, function(error) {
return Parse.Promise.error('Sorry, this profile was not found.');
}).then(function(profile) {
profile.get('_User').fetch().then(function(user) {
console.log(
'name : ' + user.get('name') + ' ' +
'age : ' + profile.get('age')
);
});
});
Note for profile.get('_User') your pointer's name may change and not be _User
Try this solution.
In User class, create a Pointer to Profile. Then you can query from User class to get Profile.
ParseQuery<User> parseQuery = ParseQuery.getQuery("User");
parseQuery.whereEqualsTo("emai", "youremail#mail.com");
parseQuery.getFirstInBackground(new GetCallback<User>() {
public void done(User user, ParseException e) {
if (e != null) {
Log.d("score", "The getFirst request failed.");
return;
}
Profile profile = (Profile) user.get("profile");
}
});
I am making very simple application in cordova for ios/android. I have made it all but I am getting problem in database. I am trying to make sure that when app run it look for internet and if internet is available than it login using api, but it also create two databases one for user login(I know it is not good practice, but my requirement will search something to encrypt database) and save only current user in database. While other db will be used to store offline working of user. But when I am not clear how to know if my db is already made. Following are codes, kindly check, as I have search everywhere and seem like there is no solution except to check tables but when I do check tables my program stops to work.
When application start it asks userid, password than after validation following commands run.
document.addEventListener("deviceready", onDeviceReady(user, serviceURL, pass), false)
Following is function I am using on deviceready, if internet available it log in user and create db, if internet is not available it access db, but here is problem how I know if db exist so let login user otherwise alert him that internet is required for first time.
function onDeviceReady(user, serviceURL, pass) {
if(navigator.network.connection.type == Connection.NONE)
{
var db = window.openDatabase(
'test',
'1.0.0',
'Test Database',
64 * 1024
);
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM USERS', [], function (tx, results) {
var len = results.rows.length, i;
msg = "<p>Found rows: " + len + "</p>";
}
alert(msg);
for (i = 0; i < len; i++){
alert(results.rows.item(i).user );
}
}, null);
});
}
else
{
alert("Internet is available!!");
$.getJSON(serviceURL + 'APIjson.php?user='+user+'&pass='+pass+'&action=login', function(data)
{
if(data.error==-1)
{
alert("Invalid user id or password.");
return false;
}
else {
//navigator.notification.activityStop(); // for android only
// ActivityIndicator.hide(); // for IOS
window.localStorage["user_id"] = data.id;
var db = window.openDatabase(
'test',
'1.0.0',
'Test Database',
64 * 1024
);
db.transaction(function populateDB(tx) {
tx.executeSql('DROP TABLE IF EXISTS USERS');
tx.executeSql('CREATE TABLE IF NOT EXISTS USERS (id unique, user, pass)');
var sql = 'INSERT INTO USERS (id, user, pass) VALUES (?, ?, ?)';
tx.executeSql(sql,[data.id, user, pass]);
},function errorCB(tx, err) {
alert("Error processing SQL: "+err);
},function successCB() {
alert("success!");
});
window.location.href = "contact.html";
return true;
}
})
}
}
I also tried to add when database retrieve on len that howmany rows came and it should alert, but it failed and my app stop to work
if (!len > 0) {
alert("Sorry you need to connect internet for first time use");
return false;
You should move the javascript redirect into the success callback:
db.transaction(function populateDB(tx) {
tx.executeSql('DROP TABLE IF EXISTS USERS');
tx.executeSql('CREATE TABLE IF NOT EXISTS USERS (id unique, user, pass)');
var sql = 'INSERT INTO USERS (id, user, pass) VALUES (?, ?, ?)';
tx.executeSql(sql,[data.id, user, pass]);
},function errorCB(tx, err) {
alert("Error processing SQL: "+err);
},function successCB(){
alert("success!");
window.location.href = "contact.html";
});
The problem is that you are navigating away from your current page before the transaction has the chance to complete. The whole point of a call back is that it is called at a later time when the async task completes. However when you navigate away from the page, the webview will simply drop all tasks on the current page and move on.
Secondly, instead of a drop create you should use:
db.executeSql('CREATE TABLE IF NOT EXISTS tableName (someID text primary key, data text)');
To see if the data was added:
I suggest you use the chrome console to interact with the db. https://developers.google.com/chrome-developer-tools/docs/remote-debugging
You will need to write a couple db.executeSql(selectStatement) eg:
db.transaction(function(tx) {
tx.executeSql("select * from table1 where theID ='"+theID+"';", [], function(tx, rs) {
for (var i=0; i < rs.rows.length; i++) {
console.log(rs.rows.item(i));
console.log('data found in db');
}
if(rs.rows.length < 1){
console.log('data not in db');
}
},function(tx,e){console.log(e.message);});
});
To see the database, use the chrome debugger:
Connect your phone in debug mode/start emulator
In chrome go to this url: chrome://inspect/
Find your app and click inspect. A new window will open up.
Click on resources tab on top.
In the left pane click WebSQL (see screenshot)