I need a Google Cloud Function that I can call from my Android app. The function should call Vision API Safe Search and return the results to the app.
My problem: The function I have is simple and it works. But ONLY from the Cloud Test-Console. Not from the android app. When I call the function from my app, with the exact same JSON as input, it says FirebaseFunctionsException: INVALID_ARGUMENT
Function:
exports.starte = async (req, res) => {
try {
let pfad = req.body.pfad;
console.log('Input is: ' + pfad);
// Imports the Google Cloud client libraries
const vision = require('#google-cloud/vision');
// Creates a client
const client = new vision.ImageAnnotatorClient();
const visionRequest = {
features: [
{ type: 'SAFE_SEARCH_DETECTION' },
],
image: { source: { imageUri: pfad } },
};
const [result] = await client.annotateImage(visionRequest);
const detections = result.safeSearchAnnotation;
console.log('Ergebnis: ' + result);
console.log('Ergebnis: ' + detections);
res.status(200).send(detections);
} catch(err) {
console.log('Error: ' + err.message);
const errorJSON = { message: 'unknown' };
res.status(400).send(errorJSON);
}
};
Android code:
Map<String, Object> data = new HashMap<>();
data.put("pfad", path);
FirebaseFunctions.getInstance()
.getHttpsCallable("nsfw_test2")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
try {
Object result = task.getResult().getData();
Log.v(TAG, "CloudFunktion: Worked!");
return result.toString();
} catch (Exception e) {
e.printStackTrace();
Log.v(TAG, "CloudFunktion: Failed!");
return e.getMessage();
}
}
});
Example JSON input that works in the Cloud Console, but not in Android:
{"pfad":"gs:\/\/xxx.appspot.com\/photos\/xxx"}
Error I get in Android:
com.google.android.gms.tasks.RuntimeExecutionException: com.google.firebase.functions.FirebaseFunctionsException: INVALID_ARGUMENT
at com.google.android.gms.tasks.zzw.getResult(com.google.android.gms:play-services-tasks##18.0.1:3)
When I check the logs in Google Cloud, it logs: Input is: undefined (When running from the app)
When I run it from the Cloud Console instead, it works and prints the path I passed.
I have absolutely no idea why this happens. I have now checked easily 30 websites and stackoverflow questions on this. To me it seems that my Android code is correct.
But why doesn't the function read the input then? Why is the input undefined? The input in the android app is not null, I log the input before I pass it in, and it is the correct file.
According to this document, the invalid argument is because we failed to specify an argument correctly:
public static final FirebaseFunctionsException.Code INVALID_ARGUMENT
Client specified an invalid argument. Note that this differs from
FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
problematic regardless of the state of the system (e.g., an invalid
field name).
You need to create callable functions, instead of using an HTTP triggered Cloud Function.
The Callable functions require to have the following format:
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
It's not the same as an HTTP triggered Cloud Function (which is what you are using). You can still access it if you use an HTTP request from the Android app, but you need to use a Callable Cloud Function.
Related
I have been experimenting with messaging APIs in Chrome extensions. I wanted to send a message from the background script to the content script and get some response back. I prepared a function just for that. Here are the two versions of that funcion:
V1:
public static async sendToContentScript<R>(tabId: number, command: CommandToContent): Promise<R> {
const payload: CommandBody = { command };
return await chrome.tabs.sendMessage(tabId, payload);
}
V2:
public static async sendToContentScript<R>(tabId: number, command: CommandToContent): Promise<R> {
return new Promise((resolve, _) => {
const payload: CommandBody = { command };
chrome.runtime.sendMessage(payload, function (response: R) {
resolve(response);
return true;
});
});
}
Version V1 works, while versions V2 does not. It throws the following error:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
I've read a few posts about that error and it's not the first time that I see it. However, I did not find any explanation that would really "click" for me, I basically don't understand why V1 works and V2 does not. It would be great if you could help me understand that.
So I have designed a basic Publisher-Subscriber model using rhea in JS that takes an API request for saving data in DB and then publishes it to a queue.
From there a subscriber(code added below) picks it up and tries to save it in a DB. Now my issue is that this DB instance goes through a lot of changes during development period and can result in errors during insert operations.
So now when the subscriber tries to push to this DB and it results in an error, the data is lost since it was dequeued. I'm a total novice in JS so is there a way to make sure that a message isn't dequeued unless we are sure that it is saved properly without having to publish it again on error?
The code for my subscriber:
const Receiver = require("rhea");
const config = {
PORT: 5672,
host: "localhost"
};
let receiveClient;
function connectReceiver() {
const receiverConnection = Receiver.connect(config);
const receiver = receiverConnection.open_receiver("send_message");
receiver.on("connection_open", function () {
console.log("Subscriber connected through AMQP");
});
receiver.on("error", function (err) {
console.log("Error with Subscriber:", err);
});
receiver.on("message", function (element) {
if (element.message.body === 'detach') {
element.receiver.detach();
}
else if (element.message.body === 'close') {
element.receiver.close();
}
else {
//save in DB
}
}
receiveClient = receiver;
return receiveClient;
}
You can use code like this to explicitly accept the message or release it back to the sender:
try {
save_in_db(event.message);
event.delivery.accept();
} catch {
event.delivery.release();
}
See the delivery docs for more info.
here's my problem :
I want to create a Google Sheets extension in which I basically extract data from a sheet in Google Sheets, that I modify using methods in node JS.
Then, having the data that I modified in a string, I want to upload that string into the client's Drive, in a csv or xml file. Therefore I don't have a local file that I can use to upload the file, just a string variable.
How do I upload that string ?
Thanks a lot, that's my first app and I'm struggling a bit.
Code
const {google} = require ('googleapis');
const keys = require ('./keys.json');
const client = new google.auth.JWT(
keys.client_email, null,
keys.private_key,
['googleapis.com/auth/drive'],
'https://www.googleapis.com/…'
);
client.authorize(function(err, tokens){
if (err){
console.log(err);
return
} else {
console.log('Connected');
gsrun(client);
}
});
async function gsrun(cl) {
const gsapi = google.sheets({version: 'v4', auth: cl});
}
You have to set your file's metadata and the data it will contain (it's important the MIME type for this case must be text/csv) and the file's body will be a simple string. This code will help you taking into consideration you already did the OAuth process and have the string you want to insert:
module.exports.init = async function (){
// Before calling the API, build your own Drive service instance
// In the second argument, you must pass your own string message
const pro = await uploadSimpleString(drive, null);
console.log(pro);
}
uploadSimpleString = (drive, message) => {
// Set file metadata and data
message = message || 'This is a simple String nice to meet you';
const fileMetadata = {'name': 'uploadSimpleStringt.csv'};
const media = {
mimeType: 'text/csv',
body: message
};
// Return the Promise result after completing its task
return new Promise((resolve, reject) => {
try{
// Call Files: create endpoint
return drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id'
},(err, results) => {
// Result from the call
if(err) reject(`Drive error: ${err.message}`);
resolve(results);
})
} catch (error){
console.log(`There was a problem in the promise: ${error}`);
}
});
}
Notice
To test this code, run it in your CLI using this command:
node -e 'require("./index.js").init()'
Where index.js is your file's name and init() is your main function.
Docs
For more info, please check these links and also consider using the [google-drive-api] tag in that way, there are more chances to receive help because more people will be able to find your question.
How to get Help
Files: create
G Suite documents and corresponding export MIME types
I'm trying to return values from Parse.Cloud.define functions in cloud code using Parse. I'm using Parse 3.0.0 and I can get it to return values from simple cloud code defines but not complex ones.
I'm coding client side iOS in Objective-C.
Here's the cloud code function (I don't care if this is unsafe, I'm not changing it)
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.first({useMasterKey:true})
.then((results) => {
Parse.User.requestPasswordReset(results.get("email"))
.then(() => {
return "good work";
}).catch((error) => {
});
})
.catch((error) => {
});
});
This works just fine, it sends the email to the user as expected by using the User's username field.
In iOS I'm calling it like this:
[PFCloud callFunctionInBackground:#"doStuff" withParameters:#{#"username" : cleanEntryData} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
NSLog(#"error %#", error);
}
}];
This call works in iOS and the email is successfully sent to the user for password reset. However, here's the problem.
when I call
NSLog(#"success %#", object);
the value in Xcode debug window is
success (null)
I expect it to be
success good work
When I a simple cloud code define like so:
Parse.Cloud.define("testing", (req) => {
return "very good";
});
with iOS like so:
[PFCloud callFunctionInBackground:#"testing" withParameters:#{#"nothing" : #"nothing"} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
}
}];
then i get the result in Xcode debugger that i'm looking for
success very good
i don't know why the "doStuff" cloud code define is not returning the string "good work" when the function is clearly executing and sending the email as it should. I've read both the Parse 3.0.0 and JS 2.0.0 guides and they aren't very descriptive on how this should work with Parse Cloud defines. I'm not a JS coder, I only code in mobile, so I'm probably doing something stupid. Any help would be great. Thanks.
There's no issue in your iOS code, the issue lies in the cloud code, so you'll need to change the cloud code, since it's not necessarily unsafe, but rather flawed.
The issue is that you are nesting Promises inside each other instead of chaining them together, hence the single nested return value is lost in the several nested layers.
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
return query.first({useMasterKey:true})
.then((results) => {
return Parse.User.requestPasswordReset(results.get("email"));
}).then(() => {
return "good work";
})
});
I have a native Android app that now has a React-Native part to it.
In short, i have a list view in native Android/Java. When i tap a row, i start a new Activity which leads to the React-Native code.
The components make an API call, but before it does it, my code goes through the React Native module to get a value from the User Preferences in the Java/Android settings for the header so that it can make the proper API call.
The React Native API call in /actions/index.js:
export const toggleBlocked = (user_id) =>
async dispatch => {
try {
let headers = await getHeaders();
const response = await fetch('http://my.domain.com/api/get_data?user_id=`${user_id}`', { headers, method: 'GET' });
if (response.status == 200) {
// do stuff
} else {
ToastAndroid.show("Looks like the response wasnt good :(", ToastAndroid.LONG, ToastAndroid.CENTER);
}
} catch (error) {
ToastAndroid.show("An Error Occurred :(", ToastAndroid.LONG, ToastAndroid.CENTER);
}
};
function getHeaders() {
let userId = UserService.getUserId()
console.log("2. React-Native | getHeaders... user_id is " + userId)
return (
new Headers({
Cookie: `abcxyz=${userId}`,
user_id: userId,
"User-Agent": "android",
"Content-Type": "application/json",
charset: "utf-8",
"app-version": "9.1.23",
Accept: "application/json"
})
)
}
Back in native Android/Java, i have my modules and packages set up. Here is UserServiceModule.java:
#ReactMethod
public int getUserId() {
SharedPreferences settings = mContext.getSharedPreferences(Constants.SettingsPreference, Context.MODE_PRIVATE);
int userId = settings.getUserId();
Log.d(TAG, "1. Java/Android code. UserID is " + userId);
return userId;
}
When i run the code, user_id is always null. I even expected my logs to show in order (see the numbered logs), but they're reverse??
"1. Java/Android code. UserID is 123"
"2. React-Native | getHeaders... user_id is 123"
Any insights would be great please. Thanks.
UPDATE / SOLUTION
Here is the updated changes to get things working correctly:
#ReactMethod
public void getUserId(final Promise promise) {
SharedPreferences settings = mContext.getSharedPreferences(Constants.SettingsPreference, Context.MODE_PRIVATE);
int userId = settings.getUserId();
Log.d(TAG, "1. Java/Android code. UserID is " + userId);
promise.resolve(Integer.toString(userId));
}
#ReactMethod functions are always suppose to return void and are asynchronous. The reason you are getting null is because your JS code is written synchronously. It initializes the variable userId as null and sets it with the return value of the asynchronous call. But because the code you written was synchronous, it will go to the next line before the react method comes back with the value.
You will need to do an async await like you did with your dispatch method. However that won't be enough as react methods return a void value. Quite frankly I don't know how a method not found error was not triggered instead. Perhaps #ReactMethod ignores the return value you defined in Java?
Regardless, after setting up the async await. Do something similar to what they did here:
https://facebook.github.io/react-native/docs/native-modules-android.html#callbacks
Set isBlockingSynchronousMethod = true in #ReactMethod
example:
#ReactMethod(isBlockingSynchronousMethod = true)
public String getString(){
return "awesome string";
}
in js file:
var someString = await NativeModule.ModuleName.getString();