AWS DynamoDB Scan failing on iOS but works on Android - android

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.

Related

Titanium Hyperloop access to android.os.SystemProperties

I have been trying a ( i hope) simple bit of Android hyperloop code directly within a titanium project (using SDK 7.0.1.GA and hyperloop 3).
var sysProp = require('android.os.SystemProperties');
var serialNumber = sysProp.get("sys.serialnumber", "none");
But when the app is run it reports
Requested module not found:android.os.SystemProperties
I think this maybe due to the fact that when compiling the app (using the cli) it reports
hyperloop:generateSources: Skipping Hyperloop wrapper generation, no usage found ...
I have similar code in a jar and if I use this then it does work, so I am wondering why the hyperloop generation is not being triggered, as I assume that is the issue.
Sorry should have explained better.
This is the jar source that I use, the extraction of the serial number was just an example (I need access to other info manufacturer specific data as well), I wanted to see if I could replicate the JAR functionality using just hyperloop rather that including the JAR file. Guess if it's not broke don't fix it, but was curious to see if it could be done.
So with the feedback from #miga and a bit of trial and error, I have come up with a solution that works really well and will do the method reflection that is required. My new Hyperloop function is
function getData(data){
var result = false;
var Class = require("java.lang.Class");
var String = require("java.lang.String");
var c = Class.forName("android.os.SystemProperties");
var get = c.getMethod("get", String.class, String.class);
result = get.invoke(c, data, "Error");
return result;
}
Where data is a string of the system property I want.
I am using it to extract and match a serial number from a Samsung device that is a System Property call "ril.serialnumber" or "sys.serialnumber". Now I can use the above function to do what I was using the JAR file for. Just thought I'd share in case anyone else needed something similar.
It is because android.os.SystemProperties is not class you can import. Check the android documentation at https://developer.android.com/reference/android/os/package-summary.html
You could use
var build = require('android.os.Build');
console.log(build.SERIAL);
to access the serial number.

Angular slow to build an array on Android device

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

React Native Synchronous Secure Random Number Generation

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');

Parse for Android: ParseInstallation.getCurrentInstallation().get(KEY_DEVICE_TOKEN) returns null

Parse for Android: Trying to get a device token in Parse but it keeps returning null. This code was working about 6 months back but lately have noticed this issue. Using the device token to subscribe to Parse later on. It just gets stuck in the while loop.I am using Parse 1.7.1 version. Even if I update the parse will this be the right way to get the device token?
private static final String KEY_DEVICE_TOKEN = "deviceToken";
boolean isTokenReady = false;
while (!isTokenReady) {
String deviceToken = (String) ParseInstallation.getCurrentInstallation().get(KEY_DEVICE_TOKEN);
if (!StringHelper.isNullOrEmpty(deviceToken)) {
isTokenReady = true;
} else {
sleep(1000);
}
}
ParsePush.subscribeInBackground("pushtoken_" + deviceToken);
You can use this, if you are retrieving a String:
ParseInstallation.getQuery().get(objectId).getString(KEY_DEVICE_TOKEN)
If you need to get the objectId from the default installation class:
ParseInstallation.getCurrentInstallation().getObjectId();
I'm using version 1.9.2. Hope this helps!
There's been 11 updates of the Android parse sdk. I would definitely update since there's lots of fixes.
Also, you shouldn't have to block your thread to wait for the device token. Did you forgot to save the installation before trying to get the deviceToken?
Like this:
ParseInstallation.getCurrentInstallation().save();
String deviceToken = (String) ParseInstallation.getCurrentInstallation().get( "deviceToken" );
Lastly, Im not sure why you would use a unique device token as push channels. You can use the deviceToken directly. So I would suggest not to subscribe to any channels and push notifications to selected devices using their deviceTokens.
I spent a lot of time on this problem too...
getInstallationId() seems to work. I use installationId to query installations and now it works OK

getEntity call results in crash (using odata4j on a WCF service)

I am trying out odata4j in my android app to retrieve data from a DB that can be accessed from a WCF service.
ODataConsumer co = ODataConsumer.create("http://xxx.xx.xx.xxx:xxxx/Users");
for(OEntity user : co.getEntities("Users").execute())
{
// do stuff
}
However this crashes at the call to getEntities. I have tried a variety of other calls as well, such as
Enumerable<OEntity> eo = co.getEntities("Users").execute();
OEntity users = eo.elementAt(0);
However this also crashes at eo.elementAt(0).
The logcat doesn't tell me anything, and the callstack seems to be Suspended at ActivityThread.performLaunchActivity.
Entering "http://localhost:xxxx/Users" in my web browser on the other hand works as expected and returns the users in my DB in xml format.
Any ideas on how I can debug this?
To log all http requests/responses:
ODataConsumer.dump.all(true);
The uri passed to the consumer .create call should be the service root. e.g. .create("http://xxx.xx.xx.xxx:xxxx/"); Otherwise your code looks fine.
Note the Enumerable behaves like the .net type - enumeration is deferred until access. If you plan on indexing multiple times into the results, I'd suggest you call .toList() first.
Let me know what you find out.
Hope that helps,
- john
I guess the call should be:
ODataConsumer co = ODataConsumer.create("http://xxx.xx.xx.xxx:xxxx");
for(OEntity user : co.getEntities("Users").execute())
{
// do stuff
}
create defines service you want to connect but Users is the resource you want to query.
Can you try this way.
OEntity oEntity;
OQueryRequest<OEntity> oQueryRequest= oDataJerseyConsumer.getEntities(entityName);
List<OEntity> list= oQueryRequest.execute().toList();
for (OEntity o : list) {
List<OProperty<?>> props = o.getProperties();
for (OProperty<?> prop : props) {
System.out.println(prop.getValue().toString());
}
}

Categories

Resources