Observers hanging when using Firebase offline - ios

I'm trying to add offline capabilities to our iOS app (simply put, an app that manages users and projects), which relies on Firebase. This is using Swift 3.0. I have followed the guides and did the following:
Added this to my application delegate, right after FIRApp.configure():
FIRDatabase.database().persistenceEnabled = true
called keepSynced(true) on users/userKey and all of said user's projects/projectKey nodes.
The app works fine while online (obviously) and keeps working equally well offline, even if I restart it while disconnected from the internet. The problem arises when I create a new project while offline. I use the following to create a new project:
let projectKey = FIRDatabase.database().reference(withPath: "projects").childByAutoId().key
let logsKey = FIRDatabase.database().reference(withPath: "projects").child(projectKey).child("logs").childByAutoId().key
FIRDatabase.database().reference().updateChildValues([
"projects/\(projectKey)/key1" : value1,
"projects/\(projectKey)/key2" : [
"subkey1" : subvalue1,
"subkey2" : subvalue2
],
"projects/\(projectKey)/key3/\(logKey)" : [
"subkey3" : subvalue3,
"subkey4" : subvalue4
]
]) { error, ref in
if error != nil {
print("Error")
return
}
}
After the project's creation, if I attempt to call observeSingleEvent on "projects/projectKey/key1" or "projects/projectKey/key2", all is good. However, calling the same function on "projects/projectKey/key3/logKey" never triggers the block/callback - it only gets called if the connection comes back.
I have enabled the FIRDatabase logging (which confirms the local write takes place) to look for hints but can't seem to figure out the issue.
Is there something I'm missing?
Note: Using the latest Firebase iOS SDK (3.5.2).
EDIT: It seems to work fine if I expand the deep links:
let projectKey = FIRDatabase.database().reference(withPath: "projects").childByAutoId().key
let logsKey = FIRDatabase.database().reference(withPath: "projects").child(projectKey).child("logs").childByAutoId().key
FIRDatabase.database().reference().updateChildValues([
"projects/\(projectKey)" : [
"key1" : value1,
"key2" : [
"subkey1" : subvalue1,
"subkey2" : subvalue2
],
"key3" : [
logKey : [
"subkey3" : subvalue3,
"subkey4" : subvalue4
]
]
]
]) { error, ref in
if error != nil {
print("Error")
return
}
}
Could this be a bug in the way Firebase manages its local cache states when offline? It's as if it didn't know about intermediate keys created using deep links in updateChildValues.

Related

Amplify custom configuration error on iOS Swift

I'm trying to integrate Amplify to my project but I'm having some issues with the configuration.
The backend is sending the S3 Storage configuration to my project so I have to configurate Amplify with the data received.
I tried to configurate the storage following this test but It's failing with the following error:
PluginError: Unable to decode configuration
Recovery suggestion: Make sure the plugin configuration is JSONValue
▿ pluginConfigurationError : 3 elements
- .0 : "Unable to decode configuration"
- .1 : "Make sure the plugin configuration is JSONValue"
- .2 : nil
This is my code:
func amplifyConfigure() {
do {
Amplify.Logging.logLevel = .verbose
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.add(plugin: AWSS3StoragePlugin())
let storageConfiguration = StorageCategoryConfiguration(
plugins: [
"awsS3StoragePlugin": [
"bucket": "bucket",
"region": "us-west-2",
"defaultAccessLevel": "protected"
]
]
)
let amplifyConfiguration = AmplifyConfiguration(storage: storageConfiguration)
try Amplify.configure(amplifyConfiguration)
// LOG success.
} catch {
// LOG Error.
}
}
Can someone help me with this custom configuration?
Thanks!
It seems that config cannot be declared directly in one go for some reason, possibly type-related. For me it works if I declare it in multiple steps. Try replacing this:
let storageConfiguration = StorageCategoryConfiguration(
plugins: [
"awsS3StoragePlugin": [
"bucket": "bucket",
"region": "us-west-2",
"defaultAccessLevel": "protected"
]
]
)
with this:
var storageConfigurationJson : [String:JSONValue] = [ "awsS3StoragePlugin" : [] ]
storageConfigurationJson["awsS3StoragePlugin"] = ["bucket": "bucket",
"region": "us-west-2",
"defaultAccessLevel": "protected"]
let storageConfiguration = StorageCategoryConfiguration(plugins: storageConfigurationJson)
I've only used Amplify config with AuthCategoryConfiguration, so in case StorageCategoryConfiguration has a different syntax you may need to adjust my suggested code accordingly.

CFSDK payment gateway 'Invalid token sent in request'

I am working on an app in which I need to integrate payment gateway and I am using Cashfree payment gateway WebView Checkout option as per the need. It is easy to implement from their docs. This is how I initiate SDK:
func initiateCFSDK() {
let cashfreeVC = CFViewController(params: getPaymentParams(), appId: self.appId, env: self.environmentCF, callBack: self)
let navVC = UINavigationController(rootViewController: cashfreeVC)
self.present(navVC, animated: true, completion: nil)
}
Payment parameters:
func getPaymentParams() -> Dictionary<String, String> {
return [
"orderId": self.orderId,
"tokenData" : self.paymentToken,
"orderAmount": self.paymentValue,
"customerName": "name",
"orderNote": "health prodcuts",
"orderCurrency": "INR",
"customerPhone": "9876543210",
"customerEmail": "abc#gmail.com",
"notifyUrl": "https://test.gocashfree.com/notify"
]
}
From their docs, we need to drag and drop framework to Xcode project and add it to Embedded Binaries. The token in generated from the backend using the orderId and need to pass it in payment parameters.
Problem 1:
Everytime I Initiate SDK it gives me error: "Invalid token sent in request" and prints following result in delegate method:
Finished navigating to url https://test.cashfree.com/billpay/checkout/post/submit
JSON value : {"orderId":"","referenceId":"","orderAmount":"","txMsg":"Invalid token sent in request","txTime":"","txStatus":"FAILED","paymentMode":"","signature":""}
Following is the screenshot for the reference.
Problem 2:
Since I present the SDK by embedding inside a UINavigationController, when I press back button it can't dismiss itself.
I am banging my head from many weeks for the error (Invalid token) I can't resolve. So anyone here tried it and please take a look what is wrong? Looking forward for the solutions from SO.
P.S: I tried contacting their tech support and everytime they just sent link to their docs.
I have prepared demo project with Cash Free SDK, Using Xcode 11.0
Step 1
To Generate the token , I have used in postman
https://test.cashfree.com/api/v2/cftoken/order
with parameters
{
"orderId":"ORD123456",
"orderAmount":"30",
"orderCurrency":"INR"
}
with following headers
Content-Type:application/json
X-Client-Id:XXXXXXX
X-Client-Secret:XXXXXX
Step 2
Now In code
func initiateCFSDK() {
let cashfreeVC = CFViewController(params: getPaymentParams(), appId: "xxxxxxxxxxx", env: "TEST", callBack: self)
self.navigationController?.pushViewController(cashfreeVC, animated: true)
}
func getPaymentParams() -> Dictionary<String, String> {
return [
"orderId": "ORD123456",
"tokenData" : "<<TOKEN FROM POSTMAN REQUEST>>",
"orderAmount": "30",
"customerName": "name",
"orderNote": "health prodcuts",
"orderCurrency": "INR",
"customerPhone": "9876543210",
"customerEmail": "abc#gmail.com",
"notifyUrl": "https://test.gocashfree.com/notify"
]
}
Here Nothing changed just used TEST environment and passed the appid and token
Notes:
make sure you are using TEST environment URL to generate token with TEST environment client id and client secret
also check notifyUrl
Order ID should be same
make sure you are not using old or expired tokens
Problem 2 : Don't bother to present , just push this controller :)
Cheers !! :)

Using Google Assistant Change Firebase Database Value

I Created a android app in which if a press a button and value changes in Firebase database (0/1) , i want to do this using google assistant, please help me out, i searched out but didn't found any relevant guide please help me out
The code to do this is fairly straightforward - in your webhook fulfillment you'll need a Firebase database object, which I call fbdb below. In your Intent handler, you'll get a reference to the location you want to change and make the change.
In Javascript, this might look something like this:
app.intent('value.update', conv => {
var newValue = conv.prameters.value;
var ref = fbdb.ref('path/to/value');
return ref.set(newValue)
.then(result => {
return conv.ask(`Ok, I've set it to ${newValue}, what do you want to do now?`);
})
.catch(err => {
console.error( err );
return conv.close('I had a problem with the database. Try again later.');
});
return
});
The real problem you have is what user you want to use to do the update. You can do this with an admin-level connection, which can give you broad access beyond what your security rules allow. Consult the authentication guides and be careful.
I am actually working on a project using Dialogflow webhook and integrated Firebase database. To make this posible you have to use the fulfilment on JSON format ( you cant call firebasedatabase in the way you are doing)
Here is an example to call firebase database and display a simple text on a function.
First you have to take the variable from the json.. its something loike this (on my case, it depends on your Entity Name, in my case it was "tema")
var concepto = request.body.queryResult.parameters.tema;
and then in your function:
'Sample': () => {
db.child(variable).child("DESCRIP").once('value', snap => {
var descript = snap.val(); //firebasedata
let responseToUser = {
"fulfillmentMessages": [
{ //RESPONSE FOR WEB PLATFORM===================================
'platform': 'PLATFORM_UNSPECIFIED',
"text": {
"text": [
"Esta es una respuesta por escritura de PLATFORM_UNSPECIFIED" + descript;
]
},
}
]
}
sendResponse(responseToUser); // Send simple response to user
});
},
these are links to format your json:
Para formatear JSON:
A) https://cloud.google.com/dialogflow-enterprise/docs/reference/rest/Shared.Types/Platform
B) https://cloud.google.com/dialogflow-enterprise/docs/reference/rest/Shared.Types/Message#Text
And finally this is a sample that helped a lot!!
https://www.youtube.com/watch?v=FuKPQJoHJ_g
Nice day!
after searching out i find guide which can help on this :
we need to first create chat bot on dialogflow/ api.pi
Then need to train our bot and need to use webhook as fullfillment in
response.
Now we need to setup firebase-tools for sending reply and doing
changes in firebase database.
At last we need to integrate dialogflow with google assistant using google-actions
Here is my sample code i used :
`var admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
var database = admin.database();
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.hello = functions.https.onRequest((request, response) => {
let params = request.body.result.parameters;
database.ref().set(params);
response.send({
speech: "Light controlled successfully"
});
});`

In the iOS SDK (Swift 3.0), Firebase Database Queries that make use of "queryOrdered/queryEqual", don't work as expected

When running the code below, I get an empty response, even though the corresponding data is there:
self.ref?.child("play-data/calories/GC5g4RUmy0WTTL5w3jSobefa9Ft2").
queryOrdered(byChild: "parentId").
queryEqual(toValue: "-KcpS62MR-73MozKJEVt").
observeSingleEvent(of: .value, with: { (snapshot) in
print("ITEMS \(snapshot.childrenCount)")
}) { (error) in
print("ERROR :: \(error)")
}
The data looks like this:
{
"play-data" : {
"calories" : {
"GC5g4RUmy0WTTL5w3jSobefa9Ft2" : {
"-KcpTSo0KrnNIzmAAD9O" : {
"endTime" : 1486955567572,
"id" : "-KcpTSo0KrnNIzmAAD9O",
"parentId" : "-KcpS62MR-73MozKJEVt",
"startTime" : 1486955550331,
"value" : 1.328500509262085
},
"-KcpTT---0Zu-0eTd4a8" : {
"endTime" : 1486955627572,
"id" : "-KcpTT---0Zu-0eTd4a8",
"parentId" : "-KcpS62MR-73MozKJEVt",
"startTime" : 1486955567572,
"value" : 4.62333345413208
},
"-KcpTT-1SvZrScKdceLC" : {
"endTime" : 1486955636994,
"id" : "-KcpTT-1SvZrScKdceLC",
"parentId" : "-KcpS62MR-73MozKJEVt",
"startTime" : 1486955627572,
"value" : 0.7260898947715759
}
}
}
So based on the data, it should print 3 but does 0. Permissions are configured correctly since I have Android and web implementations that work fine with this data. Any ideas?
OK, looks like this was the result of my local and cloud versions of the data being out of sync and therefore the inconsistent results during testing (where I had to run tests, replace the post-test data with an older backup, repeat) I fixed these issues by adding this line to my init code:
ref?.child("play-data").keepSynced(true)
I already had this one before:
db?.persistenceEnabled = true
Where ref and db are references to FIRDatabaseReference/FIRDatabase.

Firebase: Is there a way to see the JSON payload being sent from the SDK?

I've been developing an iOS app using Firebase for a few months, and I've run into an issue where a call to my backend is failing. To test the issue, I open up the simulator on Firebase and simulate the call, and it succeeds with no issues. All other calls work as expected.
The only conclusion I can come to is that the JSON payload differs, in some way, from what I expect it to be.
Some details:
The view controller I'm in is doing continuous observing of a data structure on the backend (using observeEventOfType:withBlock:withCancelBlock:), and checking boxes on that screen manipulates child values of that same data structure. So each time a box is checked or unchecked, it does a setValue: call with the new value. Then, since the parent data structure is being observed, it gets updated, and the screen is refreshed.
The problem I'm having is that the setValue: call get rejected, which in turn hits the cancelBlock of the other method. When I try to simulate that setValue: on my Firebase dashboard, it succeeds.
I've added the relevant backend rules and the failing method in question in a github gist here:
https://gist.github.com/jakehawken/4a4bb8d2f58c651d7310b3a1737bf11e
//RELEVANT BACKEND RULES FOR THE FAILING CALL (I'm writing to the "completed" path):
"subtasks": {
"$list_item": {
".validate": "newData.hasChildren(['subtaskDescription', 'completed'])",
"subtaskDescription" : {
".validate" : "newData.isString()"
},
"completed" : {
".validate" : "newData.isNumber() && !newData.hasChildren()"
}
}
}
METHOD CALL THAT IS FAILING (Objective-C):
- (KSPromise *)markSubtask:(HDInProgressSubtask *)subtask completed:(BOOL)completed forListID:(NSString *)listID inProgressItemKey:(NSString *)inProgressKey subtaskKey:(NSString *)subtaskKey
{
KSDeferred *deferred = [KSDeferred defer];
FIRDatabaseReference *specificSubtaskReference = [self specificSubtaskCompletionReferenceForListID:listID inProgressItemKey:inProgressKey subtaskKey:subtaskKey];
FIRDatabaseReference *subtaskCompletionReference = [specificSubtaskReference child:kCompleted];
NSNumber *value = [NSNumber numberWithBool:completed];
[subtaskCompletionReference setValue:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
if (error)
{
[deferred rejectWithError:error]; //Failing case
}
else
{
[deferred resolveWithValue:#(completed)]; //Success case
}
}];
return deferred.promise;
}
The call should simply be sending up the number 1 or a 0 (a BOOL wrapped in an NSNumber), but when I turn on verbose logging, it says it's sending up this behemoth:
{
"d" : {
"a" : "p",
"b" : {
"d" : 1,
"p" : "<the url path for this upload>"
},
"r" : 11
},
"t" : "d"
}
The apparent success from the simulator:
Closest you can get is by enabling debug logging:
FIRDatabase.setLoggingEnabled(true)
I finally figured it out myself.
I commented out all of my validation rules so that I could post anything I wanted to the backend. I had figured, up to this point, that since I was wrapping my boolean in an NSNumber, I figured the backend would store that value as a number. Accordingly, my rule for that path was newValue.isNumber(). It turns out, however, that Firebase automagically knows that it’s a boolean, so when I stripped out all my rules, the value that got posted to the backend was “true.” So, I rewrote the rule to be newValue.isBoolean() now it works just fine.
Moral of the story: If you initialize an NSNumber with a boolean, then when you hand that off to Firebase, the JSON payload that gets created will be made with the awareness that the NSNumber was a boolean to begin with.

Resources