for the last couple of months I've been working on my first app project with react native and Expo.
I'm ready to finally launch my app but I'm having one big issue: The app uses a premade sqlite database to read and update information, this database gets loaded into the app the first time it launches or if the version has been updated (via a simple variable). I tested the app with no issues via the Expo Client but, now that I'm trying it in a phone (via an apk) there's no db and I have no clue why it's not working
Here's the code that loads the db:
FileSystem.downloadAsync(
Asset.fromModule(require('../databases/programs.db')).uri,
`${FileSystem.documentDirectory}SQLite/programs-${version}.db`
).then(() => {
programsDB = SQLite.openDatabase(`programs-${version}.db`);
loadDB(loaded);
});
I have this in metro.config.js:
module.exports = {
resolver: {
blacklistRE: blacklist([/amplify\/#current-cloud-backend\/.*/]),
assetExts: ["db", "ttf", "png", "jpg"]
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
And this in app.json
"assetBundlePatterns": [
"src/library/assets/**/*",
"src/library/databases/*",
"src/library/fonts/*"
],
"packagerOpts": {
"assetExts": ["db"]
},
I've tried both with
require('../databases/programs.db'
and
require('library/databases/programs.db'
After the app tries to load stuff from the db I get the following error:
"Directory for /data/user/0/com.company.myApp/files/SQLite/programs-2020052401.db doesn't exist"
I also tried changing the source db to download from .db to .mp4 after an answer I read elsewhere but it doesn't do it either.
Any ideas? This is the last hurdle before I can finally launch my app.
SOLUTION
After tinkering with it to isolate the problem I ended up finding out that the issue was that the directory (SQLite) where the database is going to be saved in the devices wasn't being created with downloadAsync, which I would have known if I had read the docs more carefully.
I just had to make the directory first if it didn't exist and then it worked alright.
Here's how the coded ended up looking:
// Check if the directory where we are going to put the database exists
let dirInfo;
try {
dirInfo = await FileSystem.getInfoAsync(`${FileSystem.documentDirectory}SQLite`);
} catch(err) { Sentry.captureException(err) };
if (!dirInfo.exists) {
try {
await FileSystem.makeDirectoryAsync(`${FileSystem.documentDirectory}SQLite`, { intermediates: true });
} catch(err) { Sentry.captureException(err) }
};
// Downloads the db from the original file
// The db gets loaded as read only
FileSystem.downloadAsync(
Asset.fromModule(require('../databases/programs.db')).uri,
`${FileSystem.documentDirectory}SQLite/programs${version}.db`
).then(() => {
programsDB = SQLite.openDatabase(`programs${version}.db`); // We open our downloaded db
loadDB(loaded); // We load the db into the persist store
}).catch(err => Sentry.captureException(err));
Related
Every device we test SQLite.openDatabase on works fine except galaxy s10. I have tried every possible combination I can think of and it always just returns failed to open database. Any ideas on how to debug this? These are only a small subset of what I tried
SQLite.openDatabase(
{
name: 'my_db.db',
createFromLocation: 1,
},
() => {
console.log("OPEN DATABASE SUCCESS : ")
},
error => {
console.log("OPEN DATABASE ERROR : " + error);
}
);
in place of the 2 we tried 1, 3, 4, default, library, databases, /data/data/<app_name>/my_db.db data/data/<app_name>/my_db.sqlite
also tried in the formats like
SQLite.openDatabase({name: 'test.db', createFromLocation : path, location: 1}, this.openCB, this.errorCB);
SQLite.openDatabase("test.db", "1.0", "Test Database", 200000, this.openCB, this.errorCB);
the path was tried with ~/test.db also with
var RNFS = require('react-native-fs');
var path = RNFS.DocumentDirectoryPath + '/my_db.db';
And about 100 more things from every corner of the internet we could find. It should also be noted that
RNFS.writeFile(path, ''); works totally fine. Again works on every device and emulator except galaxy s10. What stupid thing am I missing?
EDIT:
I did an output off all the react native fs locations. /data/data wasn't an options. Which is supposed to be the correct version?
LOG CachesDirectoryPath /data/user/0/com.<app_name>/cache
LOG DocumentDirectoryPath /data/user/0/com.<app_name>/files
LOG DownloadDirectoryPath /storage/emulated/0/Download
LOG ExternalCachesDirectoryPath /storage/emulated/0/Android/data/com.<app_name>/cache
LOG ExternalDirectoryPath /storage/emulated/0/Android/data/com.<app_name>/files
LOG ExternalStorageDirectoryPath /storage/emulated/0
LOG FileProtectionKeys undefined
LOG LibraryDirectoryPath undefined
LOG MainBundlePath undefined
LOG PicturesDirectoryPath /storage/emulated/0/Pictures
LOG TemporaryDirectoryPath /data/user/0/com.<app_name>/cache
Edit2:
I added code to first create a databases folder, then if successful try touching the database file, byt writing a file with an empty string. All of which were successfuly. But the sqlite open still fails.
RNFS.mkdir(DirectoryPath).then((result) => {
console.log('Wrote folder!', result);
const dvfile = 'my_db.db';
console.log("Final db location = "+DirectoryPath+dvfile);
RNFS.writeFile(DirectoryPath+dvfile, "").then((result) => {
console.log("Was able to touch db file!?\n");
SQLite.openDatabase({name: dvfile, createFromLocation : DirectoryPath+dvfile, location: DirectoryPath+dvfile}, this.openCB, this.errorCB);
}).catch((err) => {
console.warn('Failed to touch the db file', err)
})
}).catch((err) => {
console.warn('Failed to write folder', err)
})
Try const db = SQLite.openDatabase('db.db'); with db.db in the same directory. Then, try to execute a small sql cmd.
db.transction(
(tx) => tx.executeSql("create table name;"),
(e) => console.error(e),
() => console.log("success")
);
I am now using the below cloud code to only update "downloads" column on my parse server running on AWS EC2 instance. But I am getting the error code 141(invalid function)
Parse.Cloud.define("updateDownloads", async (request) => {
const query = new Parse.Query(request.params.className);
query.get(request.params.objectId)
.then((watchFace) => {
downloads = watchFace.get("downloads")
watchFace.set("downloads", downloads + 1);
await watchFace.save(null, { useMasterKey: true });
return "download updated";
}, (error) => {
return "something went wrong";
});
});
I have place my code in /opt/bitnami/cloud/main.js.
I even tried adding “cloud”: “/opt/bitnami/cloud/main.js” in config.json file but then the parse server gives 503 Service Unavailable error. So I removed it.
If you don't add the cloud code main.js file to your parse server configuration, parse server will never find your function, and that's why you get the invalid function error.
If you get error when adding the file, you are either adding it in a wrong way (you need to check your parse server initialization code) or the config.json is in wrong format or the cloud code has a problem.
The best way to figure it out is by checking your logs.
At a first glance, a problem that I see (may have others) is the usage of await in a function that is not async. You are also using a combination of async and then, which is little strange.
I'd recommend you to change the code to something like:
Parse.Cloud.define("updateDownloads", async (request) => {
const query = new Parse.Query(request.params.className);
const watchFace = await query.get(request.params.objectId);
const downloads = watchFace.get("downloads");
watchFace.set("downloads", downloads + 1); // You can use inc function to avoid concurrency problem
await watchFace.save(null, { useMasterKey: true });
return "download updated";
});
Due to the recent update from Google Play Store, advising that all apps must now be 64 bit Compliant to be served by the Play Store, I have attempted to update our Cordova Android application to 64 bit.
Following, Google's advice, we have determined that there is only one of our cordova plugins that is not 64 Bit Compliant. However, this is causing painful issues.
The plugin in question, was the cordova-sqlcipher-adapter. We relied on this to encrypt our SQLite databases and to serve the databases to the application. We have now removed the reliance on this plugin for the encryption aspect. Therefore, it frees us up to upgrade to 64 bit.
When attempting to upgrade this, we realised that this plugin is built upon another plugin, cordova-sqlite-storage which handles the opening databases and executing commands. Therefore, to simplify things, we removed the cordova-sqlcipher-adapter and added the cordova-sqlite-storage plugin to ensure no issues were raised from the cipher aspect.
When running the application, using the new plugin, at a 64 bit compliant version, the app errors when attempting to run queries on one of the databases.
The error returned is:
Error: a statement error callback did not return false: no such table: RNM_Setting (code 1): , while compiling: SELECT s.Value AS [Value] FROM <MYTABLE> s WHERE s.[Key] = <PARAM>
We have tried different versions but always end up with the same issue and cannot find another way to interact with SQLite databases from a Cordova Android Application.
We have confirmed that the database in question exists in the correct directory and is populated with data and the table in question. We have even pulled the database out and run the exact query on it which succeeds so it cannot be a database issue.
The code used to open the database is:
SQLiteWrapper.prototype.OpenDatabase = function () {
var self = this;
if (this.db === null && this.hasSqlite) {
this.db = window.sqlitePlugin.openDatabase({ name: this.dbName, iosDatabaseLocation: 'Documents' });
}
};
this.GetSettingValue = function (settingKey, successCallback, failCallback) {
var self = this;
try {
var sql = ["SELECT ",
" s.Value AS [Value] ",
"FROM ",
" <MYTABLE> s ",
"WHERE ",
" s.[Key] = ? "];
sql = sql.join("");
var params = [settingKey];
this.sql.GetSingleItem(sql, params, this.ReadSettingFromDb, successCallback, fail);
} catch (e) {
fail(e);
}
function fail(e) {
self.CallbackError(failCallback, "GetSettingValue", e);
};
}
SQLiteWrapper.prototype.GetSingleItem = function (sql, params, rowRead, successCallback, failCallback) {
var self = this;
try {
this.OpenDatabase();
this.db.transaction(function (tx) {
tx.executeSql(sql, params, executeSuccess, executeFail);
}, function (e) {
fail(e);
});
function executeSuccess(tx, rs) {
var item = null;
try {
if (rs.rows.length > 0) {
var row = rs.rows.item(0);
item = rowRead(row);
}
successCallback(item);
} catch (e) {
fail(e);
}
}
function executeFail(tx, e) {
fail(e);
}
} catch (e) {
fail(e);
}
function fail(e) {
self.CallbackError(failCallback, "GetSingleItem", e);
}
};
this.ReadSettingFromDb = function (row) {
return row.Value;
};
We are at a bit of a loss now as to how to interact with a SQLite database in a 64 Bit Compliant way. Any help to achieve this would be greatly appreciated.
When you've used a cipher-driver before, you either need to de-crypt the database or start over with a new one database. There might be a table RNM_Setting, but without decryption it behaves as if it would not exist. Ever tried opening that file on a computer with "DB Browser for SQLite" ?
Besides, your reasoning concerning 64 bit does not make much sense, simply because android-database-sqlcipher-ndk.jar has an arm64-v8a/libsqlcipher.so.
While developing a Progressive-Web-App the following Problem occurred:
Standalone mode works perfectly without including the service worker - but does NOT work with.
Without Service-Worker a2hs (added to Homescreen) PWA gets correctly started in "standalone"-Mode.
After adding the Service-Worker (a2hs + installed / Web-APK) PWA opens new Tab in new Chrome-Window.
Chrome-PWA-Audit:
login_mobile_tablet.jsf / include service worker:
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('../serviceWorker.js', {scope: "/application/"})
/* also tried ".", "/", "./" as scope value */
.then(function(registration) {
console.log('Service worker registration successful, scope is: ', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error: ', error);
});
}
</script>
serviceWorker.js:
var cacheName = 'pwa-cache';
// A list of local resources we always want to be cached.
var filesToCache = [
'QS1.xhtml',
'pdf.xhtml',
'QS1.jsf',
'pdf.jsf',
'login_pages/login_mobile_tablet.jsf',
'login_pages/login_mobile_tablet.xhtml'
];
// The install handler takes care of precaching the resources we always need.
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
})
);
})
// The activate handler takes care of cleaning up old caches.
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
// The fetch handler serves responses for same-origin resources from a cache.
self.addEventListener('fetch', event => {
// Workaround for error:
// TypeError: Failed to execute 'fetch' on 'ServiceWorkerGlobalScope': 'only-if-cached' can be set only with 'same-origin' mode
// see: https://stackoverflow.com/questions/48463483/what-causes-a-failed-to-execute-fetch-on-serviceworkerglobalscope-only-if
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin')
return;
event.respondWith(
caches.match(event.request, {ignoreSearch: true})
.then(response => {
return response || fetch(event.request);
})
);
});
manifest.json:
{
"name":"[Hidden]",
"short_name":"[Hidden]",
"start_url":"/application/login_pages/login_mobile_tablet.jsf",
"scope":".",
"display":"standalone",
"background_color":"#4688B8",
"theme_color":"#4688B8",
"orientation":"landscape",
"icons":[
{
"src":"javax.faces.resource/images/icons/qsc_128.png.jsf",
"sizes":"128x128",
"type":"image/png"
},
{
"src":"javax.faces.resource/images/icons/qsc_144.png.jsf",
"sizes":"144x144",
"type":"image/png"
},
{
"src":"javax.faces.resource/images/icons/qsc_152.png.jsf",
"sizes":"152x152",
"type":"image/png"
},
{
"src":"javax.faces.resource/images/icons/qsc_192.png.jsf",
"sizes":"192x192",
"type":"image/png"
},
{
"src":"javax.faces.resource/images/icons/qsc_256.png.jsf",
"sizes":"256x256",
"type":"image/png"
},
{
"src":"javax.faces.resource/images/icons/qsc_512.png.jsf",
"sizes":"512x512",
"type":"image/png"
}
]
}
The following questions / answers were considered - but no solution was found:
PWA wont open in standalone mode on android
WebAPK ignores display:standalone flag for PWA running on local network
PWA deployed in node.js running in Standalone mode on Android and iOS
Technical Background
The Moment you add your Service-Worker (along all other PWA-Requirements) your App gets created as an Real PWA - with Web-APK getting installed.
Therefore you also need to use Default-HTTPS-Port 443 - make sure you use a valid HTTPS-Certificate.
Before adding the Service-Worker, this mandatory requirement was missing so your PWA was NOT installed and therefore needed less other requirements to be displayed in "standalone-mode".
It's just a shame that this is nowhere documented... and we had to "find out" for ourselves.
Short-List of Mandatory Requirements for "Installable Web-APK":
(As we could not find a full List, i try to include all Points)
Registered Service-Worker (default-implementation like yours is enough)
manifest.json (yours is valid)
https with valid certificate
https default-port (443, eg. https://yourdomain.com/test/)
... for the rest just check chrome audit tool (HINT: you don't need to pass all requirements - your web-apk should work when switching to https-default-port)
I am new for ionic 2/3.
I have create SQLite database for my ionic2 project.
I have used following code for create data.db file for SQLite database
this.sqlite.create({name: "data.db", location: "default"}).then((db: SQLiteObject) => {
this.database = db;
this.createTables();
}, (error) => {
console.log("ERROR: ", error);
});
async createTables() {
try {
await this.database.executeSql(this.userTable, {});
} catch (e) {
console.log("Error !", e);
}
}
code is working, database open.
I want to get data.db file which created by my ionic-SQLite project. SO, I can open it in SQLiteStudio for show database in GUI form.
I have no experience with SQLiteStudio before, but I mostly use http://sqlitebrowser.org/ as a data viewer, but just to answer your question. I think the default database is stored in the private location and you will not be able to access it without the root permission.
Let's say you have created database with this method:
public getDB() {
return this.sqlite.create({
name: 'products.db',
location: 'default'
});
}
So here is what you can do:
adb exec-out run-as <your app package> cat databases/products.db > local_products.db
The above command will download the content from device to your local machine, and then you can use that file to view your data.