Facebook 3.1.1 SDK breaks A LOT

@Tom and @cebodine, I’d like to know what Tom means by _ “make it work as before.” _

Does this mean re-working the code _ “to store the facebook request context globally” _ is not necessary? I’ve been re-working my code today, because otherwise, facebook requests are failing after successfully executing a series of requests in one scene (i.e., successfully executing CASE 5 or CASE 7.) If re-working my code is not necessary, I’d rather wait for additional fix before I proceed further. So, is what Tom looking into related to this? Please let me know.

Naomi [import]uid: 67217 topic_id: 34416 reply_id: 142795[/import]

@Naomi,

This means making each call to facebook.login update the listener, whereas in the current implementation (build 1028 and newer), only the first facebook.login sets it. Before build 1028 each login call would update the listener so we introduced a regression bug with our fix. [import]uid: 7559 topic_id: 34416 reply_id: 142799[/import]

@Tom, thank you so much for the explanation. It sounds like I don’t need to rework my code then. That’s a relief.

Thanks again,
Naomi
[import]uid: 67217 topic_id: 34416 reply_id: 142802[/import]

@Tom,

Just to be clear, the listener called should be the listener that was in effect at the time that the request or dialog was sent. That is the only way that multiple concurrent Facebook requests can be correlated.

Ideally, that would mean adding an additional parameter to facebook.request to specify the listener for that specific request.

Thanks [import]uid: 120928 topic_id: 34416 reply_id: 142803[/import]

@cebodine, we are looking to fix what we broke with build 1028 changes. At this time we cannot add new parameters or APIs because it won’t be compatible with Android, which we haven’t updated to the latest SDK. We will update the Android SDK after we get some much needed Android issues out of the way. [import]uid: 7559 topic_id: 34416 reply_id: 142809[/import]

@cebodine, the login listener regression bug is fixed and should be available in the next daily build (1030). [import]uid: 7559 topic_id: 34416 reply_id: 142827[/import]

I sat down to crack this thing, and have made a completely new implementation of facebooking for Corona. No need to own enterprise or anything, just copy this and use it instead of your old lua code.

I did not bother to change any of the code, so this is how we’re using it. You need to clean it up and make adjustments for yourself.

We’re giving NO SUPPORT for this piece of code! :slight_smile:

With this Facebook implementation, you’ll get the publish_actions permission from the user and will be able to post photos, scores, achievements etc. No more facebook sdk error #5.

So why does this work? By checking the access_token validity with facebooks open graph server, we’re able to know if the access token is ok to use or not. If it is ok, then we can skip the login phase completely. Second, if the token is _not_ valid, we can clean up our facebook variables, logout the user (logout is not a global function in terms of Corona Facebooking, but app-sandboxed), and pop up the auth dialog.

How to use?

FaceSingleSignon(your\_listener)  
local obj = {  
 message = "Message",  
 source = { baseDir=system.TemporaryDirectory, filename="image.png", type="image" }  
}  
FacePublishPhoto(obj)  
local facebook = require("facebook")  
local appId = "YOUR APP ID"  
local fbListener  
local fbCommand  
local fbData  
local fbCallback  
local fbId  
  
local LOGOUT = 0  
local POST\_MSG = 1  
local POST\_PHOTO = 2  
local GET\_FRIENDS = 3  
local INVITE\_REQUEST = 4  
local SINGLE\_SIGNON = 5  
local FEED\_GAME = 6  
  
local function printTable( t, label, level )  
 if label then print( label ) end  
 level = level or 1  
 if type(t) ~= "table" then  
 t = json.decode(t)  
 end  
 if t then  
 for k,v in pairs( t ) do  
 local prefix = ""  
 for i=1,level do  
 prefix = prefix .. "\t"  
 end  
  
 print( prefix .. "[" .. tostring(k) .. "] = " .. tostring(v) )  
 if type( v ) == "table" then  
 print( prefix .. "{" )  
 printTable( v, nil, level + 1 )  
 print( prefix .. "}" )  
 end  
 end  
 end  
end  
  
if simulator then  
 facebook.login = function()  
 native.setActivityIndicator(false)  
 ui.newNotification("Facebook not available in simulator...", "error")  
 end  
else  
 local old\_login = facebook.login  
  
 facebook.login = function(appId, params)  
 if not \_G.access\_token then  
 print("Got no access token")  
 old\_login(appId, fbListener, params)  
 else  
 network.request("https://graph.facebook.com/me?access\_token=" .. \_G.access\_token, "GET", function(event)  
 printTable(event, "facebook.login")  
 if not event.isError then  
 local data = event.response or "{}"  
 data = json.decode(data)  
  
 if data and data.error and data.error.type == "OAuthException" then  
 print("OAuthException")  
 \_G.access\_token = nil  
 facebook.logout()  
 old\_login(appId, fbListener, {"publish\_actions"})  
 return false  
 else  
 -- Everything ok! We'll completely skip the login phase by hacking Coronas native implementation  
 fbListener({type = "session"})  
 end  
 else  
 -- Something unexpected happened, go ahead with a regular login  
 print("Face, unexpected error")  
 old\_login(appId, fbListener, params)  
 end  
 end)  
 end  
 end  
end  
  
fbListener = function(event)  
 if ("session" == event.type) then  
  
 if event.phase == "logout" then  
 return false  
 end  
  
 -- Save Access Token  
 if event.token then  
 \_G.access\_token = event.token  
 end  
  
 if fbCommand == FEED\_GAME then  
 facebook.showDialog("feed", fbData)  
 end  
  
 if fbCommand == POST\_MSG then  
 facebook.showDialog("feed", fbData)  
 end  
  
 if fbCommand == POST\_PHOTO then  
 facebook.request("me/photos", "POST", fbData)  
 end  
  
 if fbCommand == GET\_FRIENDS then  
 facebook.request("me/friends", "GET", {fields = "id, name, installed"})  
 end  
  
 if fbCommand == INVITE\_REQUEST then  
 facebook.showDialog("apprequests", fbData)  
 end  
  
 if fbCommand == SINGLE\_SIGNON then  
 facebook.request("me", "GET", {fields="id,username,name"})  
 end  
  
-----------------------------------------------------------------------------------------  
  
 elseif "request" == event.type then  
 native.setActivityIndicator(false)  
  
 if not event.isError then  
  
 local response = json.decode(event.response)  
 if response then   
 if fbCommand == POST\_MSG then  
 ui.newNotification(t("notification.facebook\_message\_posted"))  
  
 elseif fbCommand == POST\_PHOTO then  
 ui.newNotification(t("notification.facebook\_message\_posted"))  
  
 elseif fbCommand == GET\_FRIENDS then  
 fbCallback(response)  
  
 elseif fbCommand == SINGLE\_SIGNON then  
 fbCallback(response)  
  
 end  
 else  
 ui.newNotification(t("notification.facebook\_request\_failed"), "error")  
 end  
 else  
 -- Error  
 facebook.logout()  
 ui.newNotification(t("notification.facebook\_request\_failed"), "error")  
 fbCallback = nil  
 fbData = nil  
 fbCommand = nil  
 end  
 elseif "dialog" == event.type then  
 native.setActivityIndicator(false)  
  
 if event.didComplete == true then  
 if fbCommand == FEED\_GAME or fbCommand == POST\_MSG then  
 if string.find(event.response or "", "fbconnect://success?post\_id=", 1, true) then  
 ui.newNotification(t("notification.facebook\_message\_posted"))  
 end  
  
 elseif fbCommand == INVITE\_REQUEST then  
 local status = "success"  
 if ios and not string.find(event.response or "", "request=", 1, true) then  
 status = "error"  
 end  
 fbCallback(status)  
 end  
 end  
  
 fbCallback = nil  
 fbData = nil  
 fbCommand = nil  
  
 end  
end  
  
-- Publish a feed  
function FacePublishFeedGame(data)  
 fbCommand = FEED\_GAME  
 fbData = data  
 facebook.login(appId)  
end  
  
function FacePublishFeed(data)  
 fbCommand = POST\_MSG  
 fbData = data  
 facebook.login(appId)  
end  
  
function FacePublishPhoto(data)  
 fbCommand = POST\_PHOTO  
 fbData = data  
 facebook.login(appId)  
end  
  
function FaceInviteFriends(data, cb)  
 fbCommand = INVITE\_REQUEST  
 fbData = data  
 fbCallback = cb  
 facebook.login(appId)  
end  
  
function FaceGetFriends(cb)  
 fbCommand = GET\_FRIENDS  
 fbCallback = cb  
 facebook.login(appId)  
end  
  
function FaceSingleSignon(cb)  
 fbCommand = SINGLE\_SIGNON  
 fbCallback = cb  
 facebook.login(appId, {"publish\_actions"})  
end  
  
function FaceLogout()  
 \_G.access\_token = nil  
 facebook.logout()  
end  
  
end  

Enjoy a working Facebook implementation again! [import]uid: 21746 topic_id: 34416 reply_id: 142075[/import]

Hi,

1030 works fine, but broke our own plugin for facebook native share dialog, since you have moved your own code into a plugin (Enterprise).

Error Domain=com.facebook.sdk Code=7 “The operation couldn’t be completed. (com.facebook.sdk error 7.)” UserInfo=0x1ab85fd0 {com.facebook.sdk:NativeDialogReasonKey=NativeDialogInvalidForSession}

Thus we need this piece of code inserted in your plugin asap (we should have submitted to Apple today, but need to wait for you to update):

#import <facebooksdk><br>#import <facebooksdk><br><br>int<br>PluginNativeFacebook::ShowDialogForReal( lua_State *L)<br>{<br> UIViewController *viewController = fRuntime.appViewController;<br> <br> const char *txt = lua_tostring( L, 1 );<br> const char *img = lua_tostring( L, 2 );<br> const char *url = lua_tostring( L, 3 );<br> int listenerIndex = 4;<br> <br> if (NULL == fListener) {<br> if ( CoronaLuaIsListener( L, listenerIndex, kEvent ) )<br> {<br> CoronaLuaRef listener = CoronaLuaNewRef( L, listenerIndex );<br> fListener = listener;<br> }<br> }<br> <br> [FBNativeDialogs<br> presentShareDialogModallyFrom:viewController<br> initialText:[NSString stringWithFormat:@"%s", txt]<br> image:[UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%s", img]]<br> url:[NSURL URLWithString:[NSString stringWithFormat:@"%s", url]]<br> handler:^(FBNativeDialogResult result, NSError *error) {<br> <br> // Only show the error if it is not due to the dialog<br> // not being supporte, i.e. code = 7, otherwise ignore<br> // because our fallback will show the share view controller.<br> if (error &amp;&amp; [error code] == 7) {<br> NSLog(@"Not supported");<br> NSLog(@"%@", error.debugDescription);<br> <br> char status[] = "error";<br> DispatchEvent(true, status);<br> } else {<br> NSLog(@"Supported, what next?");<br> NSString *alertText = @"";<br> if (error) {<br> alertText = [NSString stringWithFormat:<br> @"error: domain = %@, code = %d",<br> error.domain, error.code];<br> <br> } else if (result == FBNativeDialogResultSucceeded) {<br> NSLog(@"Posted successfully");<br> char status[] = "success";<br> DispatchEvent(true, status);<br> } else if (result == FBNativeDialogResultCancelled) {<br> NSLog(@"Cancelled");<br> char status[] = "cancelled";<br> DispatchEvent(true, status);<br> } else if (result == FBNativeDialogResultError) {<br> NSLog(@"Got an error");<br> char status[] = "error";<br> DispatchEvent(true, status);<br> } else if (![alertText isEqualToString:@""]) {<br> NSLog(@"%@", alertText);<br> char status[] = "error";<br> DispatchEvent(true, status);<br> } else {<br> NSLog(@"Just an error, not sure why");<br> char status[] = "error";<br> DispatchEvent(true, status);<br> }<br> }<br> }];<br> <br> return 0;<br>}<br><br>int<br>PluginNativeFacebook::ShowDialog(lua_State *L)<br>{<br> NSLog( @"Show Dialog, Native Facebook Plugin, version %s.", version);<br> <br> Self *provider = ToLibrary(L);<br> provider-&gt;ShowDialogForReal(L);<br> <br> return 0;<br>}<br> [import]uid: 21746 topic_id: 34416 reply_id: 142869[/import]

If the error is generated by the invocation of your code/plugin, then this isn’t an issue with our Facebook implementation.

In other words, if you are calling your own native wrapper for facebook functionality, then our code will not have an impact on your code’s behavior. In fact, none of our code would be executed at all, since you are using your own version instead. [import]uid: 26 topic_id: 34416 reply_id: 142895[/import]

Walter,

you are holding the facebook session in your plugin, which is not accessible from our plugin. It worked perfectly before you moved your facebook code into a plugin.

We can’t force the user to login twice, one time with your plugin and one time with our plugin. So either you must implement the native share dialog in your plugin, or make the facebook session globally available like it was before. Or do you have any other suggestions? [import]uid: 21746 topic_id: 34416 reply_id: 142898[/import]

I’ve tested some more. If I haven’t logged in with Coronas facebook.login, our plugin works. As soon as we log in with facebook.login, the com.facebook.sdk Code=7 error shows up when we try to invoke the native share dialog.

However, with my code example posted, you know as well as I do that implementing the native share dialog is a 10 minute job for you at most, at least in the enterprise static library, now that you have moved the code into a plugin. And it’s even a much voted for feature in your new feature request section…

We’ll submit to Apple with daily 1027 since that works fine for us, but I sure hope you’ll do something about this soon. I mean, it took 7 weeks to solve the com.facebook.sdk Code=5 error, so maybe you can make up for that by implementing the native share dialog today :wink:

Yeah! [import]uid: 21746 topic_id: 34416 reply_id: 142901[/import]

There’s still a bug in 1030:

The operation couldn’t be completed. (com.facebook.sdk error 5.)

Steps to reproduce

  1. Login with facebook
  2. Turn off internet connection
  3. Try to do something with Facebook (request me/friends for instance)
  4. Fails with error 5
  5. Turn on internet connection
  6. Try to do something with Facebook (request me/friends for instance)
  7. Result: Facebook is now completely borked, the only way to restore Facebook operations is to exit the app and start anew.

Can the developer responsible for the facebook integration PLEASE put some pride in making a robust facebook implementation and actually TEST it? Unbelievable that we have to go so many rounds. [import]uid: 21746 topic_id: 34416 reply_id: 142912[/import]

Hey, Haakon, thank you for posting this. I’ll give it a shot and see if it works for what I need to do with mine. I appreciate you sharing your code.

Thanks again.

Naomi

Edit: Haakon, unfortunately, your workaround doesn’t work for my FB login/request flow… The initial FB login through the series of request (connecting to FB, retrieving me, posting score, retrieving app/score) works, but all subsequent requests do not work (not only retrieving me/friends or posting with showDialog but any new round of posting high score and retrieving app/score), which basically kills FB feature. In my case, with this workaround, restarting the app doesn’t fix the FB issue the way it did before. I’m still better off reverting back to how it was before. It works perfectly fine with 990, and with 1025, killing the app once fixes the issue (although, it really isn’t an option for Apple submission candidate.) That said, I’m glad it works for you. It could be that I didn’t implement your workaround exactly how it should be done, but then, my app is different from yours, and it goes through different steps. Please note, FB issue only occurs on specific FB login/request flow, i.e., CASE 5 and CASE 7. If the user logs in with permission in a separate scene from where subsequent FB login/requests take place, my app does not trigger any FB error. [import]uid: 67217 topic_id: 34416 reply_id: 142102[/import]

@Naomi, the things you say about scenes really don’t make any sense to me. The class above should be referenced in your main.lua file, and you should keep a global variable for the access_token.

main.lua

face = require("Face")  
access\_token = nil  

If you keep the code I posted as it is (but remove ui.newNotification or replace it with prints, showAlert or something else), and adds in your own shorthand functions for stuff like FacePostScore etc, using the same code style as I have done, it should work fine.

I’m quite sure you have done something to the code that makes it break, there’s no reason it should break if you stick to the patterns in my code. Don’t change facebook.login = function, for instance. That override is there by purpose. [import]uid: 21746 topic_id: 34416 reply_id: 142138[/import]

@haakon, thank you for the note. I do use a variable for access_token that is not global but accessible from any scene that I need to retrieve the token for (and I override and re-save a new token if/when FB listener returns valid event.token just to make sure.) I also require facebook as local function in each module where facebook is called for. Perhaps making it global is necessary, and maybe that’s the whole problem I encountered with your workaround…

But then, it still doesn’t make much sense, because the entire workaround I implemented takes place inside a single module/scene, and I call facebok.login with permission only once (and only if/when there’s no access_token.)

Hmmm… as I was writing, I generated a new built with the pre-workaround code. Very odd. Native alert message pops up upon initial login – which is rather very strange. I attributed this native alert to your workaround, but perhaps its something else. I built my app and tested CASE 5 & CASE 7 using daily build 990 and 1013 previously. This morning, however, I downloaded daily build 1025 (and deleted 1013) before implementing your workaround. And then I get this native alert message, and other issues. So, I reverted back to my pre-workaround code and built my app again… and it still brings up this native.alert… I thought it was your workaround that caused it to show up, but maybe it’s the latest daily build that does it, and whatever else is odd about it may have something to do with it too? I don’t know. I’ll try CASE 7 with 990 and 1013 (using my pre-workaround code.) And if it behaves the way I’m familiar with, I’ll try your workaround and see if works with daily build 1013. How confusing…

Naomi [import]uid: 67217 topic_id: 34416 reply_id: 142145[/import]

Here’s a quick update regarding the daily build 1030 as it applies to my project – it’s working perfectly for me! I tested all possible route, and everything is working smoothly. At this point, I don’t expect any aspect of FB in my app to break. Woohooo!! Thanks for the fix!

Naomi [import]uid: 67217 topic_id: 34416 reply_id: 142933[/import]

You could just check for connectivity before you do any Facebook commands, if no connectivity then display message (which you should do anyway) not perfect but would solve it most of the time. [import]uid: 8697 topic_id: 34416 reply_id: 142937[/import]

@cublah, yes, I know. The problem is that exceptions in the sdk/integration completely breaks all FB functionality, and Corona needs to handle it.

I’ve implemented the workarounds needed, and we’re using daily 1027, so right now everything works as it should for us. We’re stuck at 1027, though, until Coronalabs fixes the native share dialog. The network bug is fixed like you suggested and can stay like that. [import]uid: 21746 topic_id: 34416 reply_id: 142938[/import]

@haakon, all calls to FBSession on the Corona side are silo’d in the plugin. So as long as you don’t require our facebook library, you won’t call into the plugin, thereby avoiding any conflict.

And from what you say, it looks like you are using our plugin alongside yours, so that explains your issues. [import]uid: 26 topic_id: 34416 reply_id: 142939[/import]

@walter,

the error I found is not related to mixing 2 plugins. Mixing the plugins only affects the native share dialog.

I guess the only way we’ll get facebook integration WITH native share dialogs is to roll our own Facebook plugin from scratch? I was kind of hoping not having to do that, since you have already integrated with Facebook. Our plugin has just got the native share dialog implemented, not anything related to login, sessions or posting stuff to facebook… [import]uid: 21746 topic_id: 34416 reply_id: 142943[/import]