According to the expo sqlite documentation for react-native I can initialize a db like so:
const db = SQLite.openDatabase('db.db');
This works and I can update the db like so:
update() {
db.transaction(tx => {
tx.executeSql(
`select * from items where done = ?;`,
[this.props.done ? 1 : 0],
(_, { rows: { _array } }) => this.setState({ items: _array })
);
});
}
From my limited understanding this creates a database in the device. And then it's manipulated keeping all the db local.
I have a database with all the necessary tables already setup. How can I have it use the current database I already have setup?
For example: (not correct syntax)
const db = SQLite.openDatabaseIShipWithApp('mypath/mydb.db');
I couldn't find any documentation to help me with this.
The only reason I mention the above is because I already have the db with the tables and data.
Any help would be appreciated!
I was able to achieve this by using expo's FileSystem.downloadAsync:
first I import it since I'm using expo managed app:
import { FileSystem } from 'expo';
Then I download it from a server like so:
// load DB for expo
FileSystem.downloadAsync(
'http://example.com/downloads/data.sqlite',
FileSystem.documentDirectory + 'data.sqlite'
)
.then(({ uri }) => {
console.log('Finished downloading to ', uri)
})
.catch(error => {
console.error(error);
})
The first parameter is the uri for the location, the second one is where I'd like to place it. Here I am using documentDirectory.
If using local prepopulated database in assets:
import * as FileSystem from "expo-file-system";
import {Asset} from "expo-asset";
async function openDatabaseIShipWithApp() {
const internalDbName = "dbInStorage.sqlite"; // Call whatever you want
const sqlDir = FileSystem.documentDirectory + "SQLite/";
if (!(await FileSystem.getInfoAsync(sqlDir + internalDbName)).exists) {
await FileSystem.makeDirectoryAsync(sqlDir, {intermediates: true});
const asset = Asset.fromModule(require("../assets/database/mydb.sqlite"));
await FileSystem.downloadAsync(asset.uri, sqlDir + internalDbName);
}
this.database = SQLite.openDatabase(internalDbName);
}
This creates the SQLite directory and database if not exists. Otherwise FileSystem.downloadAsync() will throw an error on fresh installed app.
Some remarks:
You cannot use variable in require() (only string). See e.g. this.
You have to explicitly allow file extension .db or .sqlite to be loadable in Expo, see this. You have to create a file metro.config.js in root:
const defaultAssetExts = require("metro-config/src/defaults/defaults").assetExts;
module.exports = {
resolver: {
assetExts: [
...defaultAssetExts,
"db", "sqlite"
]
}
};
And may add following to app.json
"expo": {
"assetBundlePatterns": [
"**/*"
]
}
If want to delete loaded database (e.g. for testing) you have to clear whole Expo App data in Phone settings (deleting cache not sufficient). Or write a method like this:
async function removeDatabase() {
const sqlDir = FileSystem.documentDirectory + "SQLite/";
await FileSystem.deleteAsync(sqlDir + "dbInStorage.sqlite", {idempotent: true});
}
It's pretty straight forward
If you bundle your app, you have to move the Database from the asset folder to the document directory first. In order to do that, check if a folder named SQLite exists. If not, create it. Why do you need a folder called SQLite? That is because SQLite.openDatabase(databaseName) looks per default in FileSystem.documentDirectory + 'SQLite'. Then, when the folder is created, you can download the database from the asset folder. Make sure you have your database in a folder called asset. Locate the foler asset under src/asset of your app document tree. Also, make sure to configure your app.json and metro.config.js.
import * as SQLite from 'expo-sqlite';
import * as FileSystem from 'expo-file-system';
import { Asset } from 'expo-asset';
const FOO = 'foo.db'
if (!(await FileSystem.getInfoAsync(FileSystem.documentDirectory + 'SQLite')).exists) {
await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'SQLite');
};
await FileSystem.downloadAsync(
// the name 'foo.db' is hardcoded because it is used with require()
Asset.fromModule(require('../../asset/foo.db')).uri,
// use constant FOO constant to access 'foo.db' whereever possible
FileSystem.documentDirectory + `SQLite/${FOO}`
);
// Then you can use the database like this
SQLite.openDatabase(FOO).transaction(...);
// app.json
{
"name": "Your App name",
"displayName": "Your App name",
"assetBundlePatterns": [
"assets/**"
],
"packagerOpts": {
"assetExts": ["db"]
}
}
// metro config
const { getDefaultConfig } = require('#expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = {
resolver: {
assetExts: [...defaultConfig.resolver.assetExts, 'db', 'json'],
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
This is all extracted from the documentation of expo.
I don't believe this is possible in expo. There is a way to use an existing database if you are using a bare android project which involves writing some native code to copy the database from the project assets to the standard location on the phone (/data/ etc) for your application.
https://medium.com/#johann.pardanaud/ship-an-android-app-with-a-pre-populated-database-cd2b3aa3311f
I have always personally created the database myself with CREATE TABLE IF NOT EXISTS since sqlite requires you to define the schema before you query it. If you need to seed the database, this step would then be followed by additional steps to insert the required data.
In most cases, you will need to also check your reference data and update it from the server at regular intervals (it might even change from publishing your apk to someone downloading the app) and this code would also work when there is no reference data in the database.
There are a couple of services which try and take his hassle away from you (e.g. parse) but you will need to decide if you are happy with them hosting your data.) I haven't used them so not sure how this works exactly but I'm told it tries to solve the offline first type problems.
Remember that in future iterations you may need to modify the structure for future versions (to add fields etc) so you will probably need to define some code that loads when the application is first started, checks the database version and applies any changes that are required to bring the database up to the appropriate level.
Related
hay guys, i am making a react native app with expo and i am using "expo document picker" module to access the folder in the device by clicking on the button in my app , i am getting this error in my terminal --
LOG [Error: Encountered an exception while calling native method: Exception occurred while executing exported method getDocumentAsync on module ExpoDocumentPicker: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=directory }]
this is my code---
const pickDirectory = async () => {
console.log("hi!!!!!");
try {
const permission =
await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
if (permission.granted) {
const dir = await DocumentPicker.getDocumentAsync({
copyToCacheDirectory:true,
type: "directory",
});
// setSyncDirectory();
console.log(dir);
}
} catch (error) {
console.log(error);
}
};
The error you are seeing is because the DocumentPicker.getDocumentAsync() method can't find an app on your device that can open a folder.
The DocumentPicker module is meant for selecting and opening documents, not folders. If you want to let users choose a folder, you can use FileSystem.StorageAccessFramework instead. This allows you to request access to a directory and perform file operations like reading and writing files.
import * as FileSystem from 'expo-file-system';
const pickDirectory = async () => {
console.log("hi!!!!!");
try {
const permission = await FileSystem.requestDirectoryPermissionsAsync();
if (permission.granted) {
const dir = await FileSystem.StorageAccessFramework.browseForFolderAsync();
console.log(dir);
}
} catch (error) {
console.log(error);
}
};
Note that the browseForFolderAsync() method is currently only available on Android, so if you need to support iOS as well, you will need to use a different method or library to allow the user to select a folder.
I am trying to connect to Contentful's API in my react project. If I hardcode the space and access tokens it works fine, but if I try to use a .env file it's not getting the token.
Here's my code:
import {createClient} from "contentful";
export const client = () => {
//console.log(process.env)
let whatever = createClient({
space: process.env.REACT_APP_SPACE_ID ,
accessToken: process.env.REACT_APP_ACCESS_TOKEN,
});
return whatever;
};
export const getRecipes = async () => {
try{
const entries = await client().getEntries({
content_type: "recipe",
select: "fields"
});
return entries;
} catch(error){
console.log(`error fetch: ${error}`);
return;
}
}
I've tried various patches but nothing has worked. I have react-scripts installed as well.
I console logged the process.env and it doesn't have the token or space id.
I tried importing dotenv but that created some weird polyfill error that I've never seen before.
It could be that: your env file is not in the project folder but instead in the src/ folder...just check it once to make sure, it's a very easy mistake that a lot of people make.
Or if you have the client folder inside the server folder which is sometimes needed for deployment it might not be working because of that folder structure as well
I am an ex-PHP Laravel developer and I am trying to explore other options right now, currently using Node.js, Sequelize, Koa and Next.js to build a small app.
When sequelize is initialized, a models folder gets created automatically and it has along the lines the following code.
...
fs
.readdirSync(__dirname)
.filter(file => {
console.log('Filer file: ', file)
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
...
Which is working fine when imported in the main server.js file.
I am trying to figure out what is the best way to include this in any of the other files, in my case I need to have access to it via the newly release API routes from Next.js /api/{endpoint} (https://nextjs.org/docs/api-routes/introduction).
If I import the file there __dirname (from the main sequelize models file) is the root directory of my machine which makes me think that if this code is automatically generated code by their CLI tool I am probably doing something wrong.
One way I found to make this work is by passing the db object via the req object in the server.js file.
router.get('/api/*', async (ctx) => {
/**
* There has to be a better way
*/
ctx.req.db = db
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
Is there better way to handle all of this?
I want to display the current git tag on my app's login page,
Its built using react.
Im trying to use the 'git-rev-sync' library to do this.
but it doesnt seem to work on the client side because I keep getting errors like
'cannot find module 'children process', it works on the server side where Im able to console.log and print the tag
anyone know how to achieve this? Open to any solutions with any library
import version from 'git-rev-sync'
...
class Login extends Component {
...
render ()
...
return (
<div> my version: {version.tag()} </div>
) }
Thanks
I decided to use git-revision-webpack-plugin which creates a VERSION file (among other files) in the dist folder, and then I read the file from my client side react app:
add this to your webpack.js:
const GitRevisionPlugin = require('git-revision-webpack-plugin')
module.exports = {
plugins: [
new GitRevisionPlugin({
lightweightTags: true //I added this to get the tags as well
})
]
}
then my client side looks like this:
const [revision, setRevision] = useState('')
const fetchRevision = async () => {
let result = await fetch('/dist/VERSION')
let txt = await result.text()
txt = txt.replace(/^(.*?)(?:\-.*)?$/, '$1') //I only care for the tag.
setRevision(txt)
}
useEffect(() => {
fetchRevision()
}, [])
and then you can render the revision
One thing to notice, depending on your server, you may need to tell it to serve this VERSION file as is, so for example in express, you might find you need this:
server.get('*', (req, res, next) => {
if (/^\/dist\/*/.test(req.originalUrl)) {
const relative = req.originalUrl.replace(/\/dist(\/.*)/, '$1')
const filename = path.join(compiler.outputPath, relative)
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) {
return next(err)
}
res.send(result)
res.end()
})
}
...
})
Hope this helps for future use.
If you used create-react-app#0.2.3 > to generate your app.
create-react-app scripts use environment variables that start with the REACT_APP_ symbol in the root .env file. create-react-app - Adding custom environment variables is a good place to dig into the details.
or just include the following in your .env file.
.env
REACT_APP_VERSION=$npm_package_version
and access it on your react login component by referring to {process.env.REACT_APP_VERSION}
I have some template files that contain a few variable strings each, I'd like to build a very simple input form with Electron (https://www.electronjs.org/) and I want to save the composed output file on the user's computer.
Is there any module I can use to let Electron save files locally?
If you are targeting multiple platforms, I answered a similar question here. Basically app.getPath(name), app.setPath(name, path), and app.getAppPath() are very useful in saving files to the the right place regardless of the OS.
You may also want to check out these Nodejs packages which help simplify saving files directly to the host machine...
fs-jetpack
graceful-fs
Node.js fs
If you intend for users to save files you might also have a look at the Dialog api where you can specifically invoke a save dialog for that purpose.
A sample code is :
const fs = require('fs');
try { fs.writeFileSync('myfile.txt', 'the text to write in the file', 'utf-8'); }
catch(e) { alert('Failed to save the file !'); }
You can of course store the file's name as well as the content's name in variables.
This will save the content in myfile.txt, which is located inside the current working directory (which you can get through process.cwd()). If you want to write, let's say in the user's home directory, you can use the app.getPath function.
const {dialog} = require('electron').remote;
var fs = require('fs');
export default {
methods: {
save: function () {
var options = {
title: "Save file",
defaultPath : "my_filename",
buttonLabel : "Save",
filters :[
{name: 'txt', extensions: ['txt']},
{name: 'All Files', extensions: ['*']}
]
};
dialog.showSaveDialog(null, options).then(({ filePath }) => {
fs.writeFileSync(filePath, "hello world", 'utf-8');
});
},
}
}