I have a simple array that takes rows from a database and assigns a distance column as a key.
let output = {};
for (let dataRow of sqllite.rows) {
output[dataRow.distance] = dataRow;
}
In testing in Chrome browser on PC, it takes less than a second to complete, but on an Android device, it just hangs.
What's the best way to handle this?
Thanks
Mark
After a stab in the dark, this fixed it. Changing from Typescript to Javascript.
let output = {};
for (var outputIndex in sqllite.rows) {
output[sqllite.rows[outputIndex].distance] = sqllite.rows.rows[outputIndex];
}
No idea why TS is slower
Related
I am using DynamoDB as back-end database for my mobile app, and the schema etc are identical across Android & iOS. For a particular use-case, I have to perform a Scan, based on two attributes which are not indexed. For iOS Objective C, I am using the following code:
AWSDynamoDBScanExpression *scanExpression = [AWSDynamoDBScanExpression new];
scanExpression.limit = [NSNumber numberWithInt:maxCount];
scanExpression.filterExpression = #"#l = :location AND event = :event";
scanExpression.expressionAttributeNames = #{#"#l":#"location"};
scanExpression.expressionAttributeValues = #{#":location":location,
#":event":EVENT_TASTING};
Both location and event are Strings. EVENT_TASTING is a String constant. This scan keeps returning zero results, even though I have validated that for the provided entries I should be receiving the results. I use the following code in Android Java:
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
scanExpression.setLimit(maxCount);
scanExpression.addFilterCondition("location",
new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(new AttributeValue().withS(location)));
scanExpression.addFilterCondition("event",
new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(new AttributeValue().withS(Constants.EVENT_TASTING)));
The scan works as expected in Android. What needs to change in iOS to make it work there too? I updated iOS SDK to 2.3.6 but it has not made a difference. This is the only scan operation I am doing in my code.
Is there an error in my scanExpression for iOS? Is there a way I can use the Android-style syntax to make this work on iOS?
Update
I tried the following changes:
AWSDynamoDBScanExpression *scanExpression = [AWSDynamoDBScanExpression new];
AWSDynamoDBAttributeValue *locationVal = [AWSDynamoDBAttributeValue new];
locationVal.S = location;
AWSDynamoDBAttributeValue *eventVal = [AWSDynamoDBAttributeValue new];
eventVal.S = EVENT_TASTING;
scanExpression.limit = [NSNumber numberWithInt:maxCount];
scanExpression.filterExpression = #"#l = :location AND event = :event";
scanExpression.expressionAttributeNames = #{#"#l":#"location"};
scanExpression.expressionAttributeValues = #{#":location":locationVal,
#":event":eventVal};
But now I am getting an error:
The request failed. Error: [Error Domain=com.amazonaws.AWSDynamoDBErrorDomain Code=0 "(null)" UserInfo={message=ExpressionAttributeValues contains invalid value: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes for key :location, __type=com.amazon.coral.validate#ValidationException}]
Thanks to the hint from #YosukeMatsuda, I was able to fix this by calling Scan repeatedly until LastEvaluatedKey is empty. I am posting this as answer because unfortunately Mike's answer is not pointing out the correct issue and is misleading.
Here's how I changed the code in iOS:
// In a different method (for first call):
AWSDynamoDBScanExpression *scanExpression = // See code in original question
// In a new method that can be called recursively:
// DynamoDBObjectMapper scan:class-for-model expression:scanExpression
// continueWithBlock -> if (task.result):
AWSDynamoDBPaginatedOutput *paginatedOutput = task.result;
if (paginatedOutput.items.count != 0)
// Append the paginatedOutput.items to the cumulative array
else
// Replace the cumulative array with paginatedOutput.items
if (paginatedOutput.lastEvaluatedKey.count == 0) {
// Scan is complete - handle results
} else {
// Check if you have sufficient results
// In my case I had asked for 25 results but was getting 39
// So it doesn't seem to obey the scanExpression.limit value
// If more results are needed, continue the scan
[scanExpression setExclusiveStartKey:paginatedOutput.lastEvaluatedKey];
// Call this method recursively
}
If there is a more elegant solution I'd love to hear it. But at least it works now.
There are several differences between the Android code you're using and the ObjectiveC version.
in the Android Version you're using the older Filter Condition API while in the ObjectiveC you're using the more modern Filter Expression API; this doesn't necessarily make the newer one fail but it's just something to point out
in the case of ExpressionAttributeValues, the values for location and event that you're passing in should be of type AWSDynamoDBAttributeValue *, not strings; if you make this change your query will most likely start working.
I hope this answers your question but can't be certain because you only say "this works as expected in Android - how can I make it work in iOS" but you're not telling us what's broken.
For some reason, when I restart my PhoneGap app - it looses the localStorage vales that were stored before! I'm saving them in the normal way:
localStorage.setItem("foo","value");
This stores it just fine. However, when you restart the app (or leave the device off for a random amount of time), it seems to randomly loose the data. I've found a heck of a lot of posts about this - but no definative answer on how to get it to be persistent in a PhoneGap Build WebView app,
Any suggestions are much welcomed!
This seems to be quite a common problem with WebView apps:
Android 2.3.6 + Phonegap + localStorage
Android - Making Webview DomStorage persistant after app closed
I can't find a solution that works with PhoneGap Build apps though
An actual example I'm using, is:
var current_id = parseInt(currentId) + 1;
localStorage.setItem("entry_"+current_id,save_string);
localStorage.setItem("entryId",current_id);
..and then to extract it (not that this is important, as the problem is with the data going missing, and not with accessing it)
for (var i = 0; i < localStorage.length; i++){
if (localStorage.key(i).match("entry_")) {
outputString += "\n" + localStorage.getItem(localStorage.key(i));
}
}
I'm wondering if maybe upgrading from PhoneGap Build cli-5.2.0 to cli-6.0.0 may help. I will do this, and give it a whirl.
I guess another option, would be to use a SQL database to locally store the device (its just a bit trickier to setup, and means re-writing my code)
UPDATE: Not the ideal solution - but I have now moved the app over to use WebSQL for the app. It was a bit tricky to get the hang of (never used it before) - but seems to do the job, and shouldn't loose the data :)
EDIT
i tried it like this and it worked:
var current_id = parseInt(currentId) + 1;
localStorage.setItem("entry_"+current_id,save_string);
localStorage.setItem("entryId",current_id);
/*
//this is for checking, what is stored in localStorage
console.log("length: " + localStorage.length);
for(var i = 0; i < localStorage.length; i++) {
console.log(localStorage.key(i));
}
*/
var myEntryIdFromStorage = localStorage.getItem("entryId");
var myItem = localStorage.getItem("entry_" + myEntryIdFromStorage);
Old answer for clarification
How do you get your localstorage?
normally you should store items like you did:
var permanentStorage = window.localstorage;
permanentStorage.setItem("foo", "bar");
and get them back by initializing the permanentStorage the same way and:
//assuming you have permanentStorage in the same script file
//or else you have to initialize it again:
//var permanentStorage = window.localstorage;
var myItem = permanentStorage.getItem("foo");
console.log("myItem: " + myItem);
The method store item uses two parameters: the identifier and the data itself. Please check, that the identifier with which you store your data is the same as the one, with which you get it back.
Do you get any errors? Is the return (stored in my example in myItem) null or undefined or just an empty string? Does this fail in the browser or on the device?
You could clarify your question by providing more code or error messages!
I'm trying to generate keypairs in a React Native project. The key pair generation tool relies on the crypto module's random byte generation, which produces a buffer of a specified lengths with random byte values.
In order to use the crypto module within React Native, it has to be browserified, and the browserified random number generator looks like this:
https://github.com/crypto-browserify/randombytes/blob/master/browser.js
Here's the key component:
var crypto = global.crypto || global.msCrypto
if (crypto && crypto.getRandomValues) {
module.exports = randomBytes
} else {
module.exports = oldBrowser
}
Indeed, when debugging the application with Chrome, everything works fine, but when running it on iOS's JavaScriptCore engine, the oldBrowser method gets called instead, throwing the following error:
secure random number generation not supported by this browser
use chrome, FireFox or Internet Explorer 11
Thus I'm trying to find a replacement for the random bytes generation. One module I found is this one:
https://www.npmjs.com/package/react-native-randombytes
It uses the device's native libraries to generate a random number, and exposes it to React Native through their Obj-C/JS interface. It should be noted that this method only works on iOS, and the library's author doesn't have an Android solution yet, but that's an issue for another time.
This method works, in that it can generate random bytes, but it has one major drawback. React only supports asynchronous interfacing between Objective-C and JavaScript, which means that this method returns its results asynchronously. The original randomBytes method is synchronous, and pretty much every SDK out there that relies on it uses it synchronously. So if we were to go with the async version, all the SDKs would have to be rewritten for it, including all dependencies that rely on methods that used to be synchronous and now would no longer be.
Thus I'm trying to find a way to make the asynchronous native random number generator work synchronously. There are several node packages that do that, the most prominent one of them being deasync, but deasync relies on some core Node modules that cannot be browserified, so the synchronous version doesn't work.
Alternatively, I've tried wrapping it in a method that would set a semaphore, call the async generator, and wait in a while loop for the semaphore's value to change. That attempt failed because the while loop was blocking the callback from ever executing. Here's an approximation of my attempts, where the call to the async method has been replaced with a setTimeout, and the random number to be returned is a four, as determined by a fair dice roll.
function testSynchronicity() {
var isDone = false;
setTimeout(function() {
isDone = true;
}, 1000); // set isDone to true after a second
while (!isDone) {
// do nothing
}
return 4;
};
As this wasn't working, I figured I would try a completely different random number generator entirely, without the native-code-relying react-native-randombytes module and went with this one for JavaScript:
https://github.com/skeeto/rng-js
It worked fine within Node itself, but after browserifying it and trying to run the first example within React Native, it threw an error saying that the main object was not a constructor. Here's what the example looks like:
var RNG = require('./rng_react'); // rng_react is rng-js browserified
var rng = new RNG();
var randomValue = rng.random(0, 255, false);
So at this point, I'm at a bit of a loss, and would appreciate any help. Thanks!
EDIT: If all else fails, there's this, but I think it would pretty much beat the purpose of the question. https://github.com/bitpay/bitcore-lib/blob/master/lib/crypto/random.js#L37
I have found an answer that usually works. However, it is imperfect, because it works only if the randomBytes method is not required during app launch.
My solution does involve using the react-native-randombytes library. It relies on iOS's built-in CSPRNG to generate a random buffer, and then returns it asynchronously. In order to support synchronous responses, I expanded the moduel's randomBytes to not throw an error when no callback method is provided, but rather to use Stanford's JavaScript Crypto Library to generate random "words," as they're called, convert those to a buffer and then trim it accordingly:
var sjcl = require('sjcl');
var sjclRandom = new sjcl.prng(10);
var RNRandomBytes = require('react-native').NativeModules.RNRandomBytes;
module.exports.randomBytes = function(length, cb) {
if (!cb) {
var size = length;
var wordCount = Math.ceil(size * 0.25);
var randomBytes = sjclRandom.randomWords(wordCount, 10);
var hexString = sjcl.codec.hex.fromBits(randomBytes);
hexString = hexString.substr(0, size * 2);
return new Buffer(hexString, 'hex');
}
RNRandomBytes.randomBytes(length, function(err, base64String) {
if (err) {
cb(err);
} else {
cb(null, new Buffer(base64String, 'base64'));
}
});
};
The crux is, in order for the SJCL library to have sufficient entropy, it needs to have been seeded properly. So, on startup, we use the asynchronous CSPRNG functionality to seed the SJCL random number generator:
module.exports.randomBytes(4096, function(err, buffer) {
var hexString = buffer.toString('hex');
// we need to convert the hex string to bytes, or else SJCL assumes low entropy
var stanfordSeed = sjcl.codec.hex.toBits(hexString);
sjclRandom.addEntropy(stanfordSeed, 10, 'csprng');
});
Thus, we have a synchronous randomBytes method within React Native, provided we have had the opportunity to call it asynchronously at least once before we need its synchronous functionality.
Your solution does answer the question but seems a bit complex. In particular, why not use only SJCL?
In my case I've ended up using react-native-securerandom, which is just a thin wrapper over Android and iOS native calls. Then I've done this to initialise SJCL's RNG:
const { generateSecureRandom } = require('react-native-securerandom');
const sjcl = require('lib/vendor/sjcl');
const randomBytes = await generateSecureRandom(1024/8);
let temp = [];
for (let n in randomBytes) {
if (!randomBytes.hasOwnProperty(n)) continue;
temp.push(randomBytes[n].toString(16));
}
const hexSeed = sjcl.codec.hex.toBits(temp.join(''));
sjcl.random.addEntropy(hexSeed, 1024, 'generateSecureRandom');
I have a form in which user can add information and add images. The images are base64 encoded so everything is stored in a json object. This object is sent to the server (with $resource) when the user submits it.
If a user adds for example 3 Images with about 2MB per Image, has a shitty connection like Edge, and wants to upload it it seems like it's taking forever. The user just sees the $ionicLoading overlay without information how long it will take or how much % are already uploaded.
The UX is bad because the user could assume, that the app froze or is in an endless loop and that it's just a bad app.
I have the following ideas but no idea if they are possible or
Is there a way in angular, cordova or ionic to get the browserinformation how much % are already uploaded?
Is there a way to get the current uploadspeed? I could get the size of my object by getting the length of my stringified JSON Object, divide it 1024 to get the kB. Then i would check the current uploadSpeed every second and add the current uploadspeed to a variable. With this information i could calculate the uploaded % approximately
JQuery ajaxForm Plugin?
Sounds like what you are looking for is progress events from XHR2.
Assuming your server is setup to handle XHR2 and return content-length, In plain JavaScript:
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(blobOrFile);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
Upload speed is also calculable using the information returned in the progress event, as you described.
As for this implementation in AngularJS/Ionic, it seems like this is a longstanding issue within the framework that $http doesn't really support progress events.
I have seen implementations that utilize a special angular directive written for this, or even utilize a jQuery file upload implementation.
I am working on a prototype Air for Android application that loads some remote swf files (all created and controlled by me) and I need to be able to pass a variable along to the remote SWF.
Here is a quick break down of how the process currently works:
The Android app, called Loader, is installed on the device.
Loader points to my server and finds Player.swf, which it loads into the main stage. Player.swf show a login screen.
When a user logs in correctly, Player.swf receives an XML response from an API with a list of remote SWF videos to display for the user. Player.swf loops through the XML list and plays each remote SWF, one after the other.
Now, so far, so good. The remote SWF videos load up and play perfectly. But I need to start picking up variables from Player.swf (pulled from the XML response) to use in the remote SWF (things like text strings, user ID's etc)
From all the searching I have done, I believe it is down to the Sandbox environment, as Loader is a compiled application and Player.swf + the remote SWF's are all coming off a server. However, now I am wondering, as Player.swf is the one that has the AS3 code to deal with the XML response, and that is located on the same server as the remote SWF's that are played (and therefore should be the same sandbox?)
I was able to pass a variable along using a URL parameter, but it only works locally and not when I use the Android application.
I found an old blog pot about Sandbox Bridges, but the example file download doesn't work anymore.
Can anyone point me in the right direction in using the parentSandboxBridge function/class to pass along a variable (all examples I have seen deal with passing functions and/or are coded for Flex)
I cannot post any code, as I simply have nothing to show for what I want to achieve (I'm totally stumped on this bit!)
EDIT: I managed to get parameters to pass along (even though it worked locally before, it didnt work with the Loader app > Remote Player > Remote SWF animation combo)
I had to set the application domain context inside Player.swf (seeing as it is on the same server as the remote swf animations, not sure why I needed to do this)
But oddly, I cannot use parent or root to pickup variables?
I've tested this on HTC Desire running android 2.2.2 and it works what I've put in comment thus the answer is as follows:)
your host needs to define similar:
var params:URLVariables = new URLVariables();
params["string"] = "ala ma kota";
params["number"] = 1979;
var request:URLRequest = new URLRequest();
request.data = params;
request.url = "urlVarsReader.swf";
var loader:Loader = new Loader();
loader.load(request);
addChild(loader);
then loaded content (here urlVarsReader.swf) can read them as follows:
var output:TextField = new TextField();
addChild(output);
output.border = true;
output.width = 320;
output.height = 240;
output.wordWrap = true;
output.multiline = true;
var p:Object = getParams(this.loaderInfo);
for (var name:String in p)
{
output.appendText(name + " = " + p[name] + "\n");
}
I have a wrapper method for reading params:
public function getParams(li:LoaderInfo):Object
{
try
{
var params:Object = li.parameters;
var pairs:Object = {};
var key:String;
for(key in params)
{
pairs[key] = String(params[key]);
}
}
catch(e:Error)
{
return {error:e.getStackTrace()};
}
return pairs;
}
best regards
EDIT:
This works only with local content fails with SecurytyError e.g. SecurityError: Error #2070: Security sandbox violation: caller http://example.com/urlVarsReader.swf?string=ala%20ma%20kota&number=1979 cannot access Stage owned by app:/loadwithparams.swf