iOS Appcelerator Facebook Login - Login Event Doesn't Fire on Device

194 views Asked by At

When attempting to login to facebook in my app, I am experiencing a few strange behaviors.

Current stack information:

  1. Appcelerator 6.0.1.GA
  2. Appcelerator CLI 6.1.0
  3. iOS 10.1
  4. xCode 8.2.1

Strange Behaviors:

  1. login event never fires when returning from Facebook login on device.
  2. login event sometimes doesn't fire when returning from Facebook login on simulator.
  3. Setting facebook.LOGIN_BEHAVIOR_NATIVE still attempts to use browser login on device, sometimes.
  4. Sometimes on device, the app-switcher opens the Facebook app and the browser for login. This is quite vexing.

I'm actually the original contributor of the setLoginBehavior functionality of the module, though Facebook's stance seems to have changed since that contribution from "We always want you to use browser." to "We always want you to use Native.". I'm posting this question here in case someone has some insight - while I wait for an answer I will be back into the source for that module.

The only factor that I can imagine might be different from most apps is that I am using Kris Kowals Q. Here follows the code, almost verbatim from my app.

The actual function that does the login:

// linkingmodule.js
exports.linkFacebook = function() {
    var Q = require('vendor/q'),
        response = Q.defer(),
        facebook = require('facebook'),
        permissions = ['public_profile', 'user_friends', 'user_likes'];  

    facebook.initialize();
    facebook.setLoginBehavior(facebook.LOGIN_BEHAVIOR_NATIVE);
    facebook.permissions = permissions;

    facebook.addEventListener('login', function fireLogin(e) {
        if(!e.success || !facebook.loggedIn) {
            return response.reject({
                status: e.code,
                error: e.error
            });
        }

        response.resolve({
            uid: e.uid,
            data: e.data,
            token: facebook.getAccessToken()
        });
    });

    facebook.authorize();
    return response.promise;
};

The alloy controller function that calls the login function:

// login.js
function facebookLogin() {
    var remote = require('linkingmodule');

    remote.linkFacebook().
    then(function(r) {
            // do some things
        }).
        fail(function(e) {
            console.error(e);
            throw 'Unable to login with Facebook.';
        });
    }).
    fail(function(e) {
        console.error('Facebook login failed');
        console.error(e);
    });
}
1

There are 1 answers

0
trey-jones On BEST ANSWER

I have chalked this up to a bug in the Titanium module SDK, specifically Ti.fireEvent. My investigation ended with the Facebook Module, but all events within the module appeared to be sent and received as expected. Only, when Ti.fireEvent was called, it was not received in the JS app "under some circumstances". The possibility remains of a block retain cycle within the facebook module, but I was not able to solve it.

Here is my workaround:

function facebookLogin() {
    var Q = require('vendor/q'),
        response = Q.defer(),
        fb = require('facebook'),
        permissions = ['public_profile', 'user_friends', 'user_likes'];  

    var checkLoginStatus, fireLogin;

    checkLoginStatus = function(e) {
        Ti.App.removeEventListener('resumed', checkLoginStatus);
        fb.removeEventListener('login', fireLogin);

        // login often doesn't fire, so let's check on resumed as well
        if(fb.loggedIn) {
            return response.resolve({
                uid: fb.uid,
                data: null,
                token: fb.getAccessToken()
            });
        }
        console.log('resumed and found that are NOT logged in');
        return response.reject({
            status: -1,
            error: 'Did not work.'
        });
    };
    fireLogin = function(e) {
        fb.removeEventListener('login', fireLogin);
        Ti.App.removeEventListener('resumed', checkLoginStatus);
        if(!e.success || !fb.loggedIn) {
            return response.reject({
                status: e.code,
                error: e.error
            });
        }

        response.resolve({
            uid: e.uid,
            data: e.data,
            token:fb.getAccessToken()
        });
    };

    Ti.App.addEventListener('resumed', checkLoginStatus);
    fb.addEventListener('login', fireLogin);

    fb.initialize();
    fb.setLoginBehavior(fb.LOGIN_BEHAVIOR_NATIVE);
    fb.permissions = permissions;
    fb.authorize();

    return response.promise;
}

So, basically keep listening for the login event - it fires correctly under "some circumstances". But also listen for App.resumed. Whichever fires first, cancel all listeners, and check for logged in status.