fuse-open / fuse-samples

A collection of smaller examples using the various features of Fuse and FuseJS
MIT License
182 stars 153 forks source link

NativeFacebookLogin not working on Andorid #71

Open Dhvl-Golakiya opened 7 years ago

Dhvl-Golakiya commented 7 years ago

Hi,

I am trying to run NativeFacebookLogin example on both iOS and Android. It is working on iOS but getting below error in Android.

Uno.Exception: Unexpected CallbackManager, please use the provided Factory.
   at Android.Base.JNI.TryGetException(Android.Base.Primitives.JNIEnvPtr,[string])
   at Android.Base.JNI.CheckException(Android.Base.Primitives.JNIEnvPtr,[string])
   at Android.Base.JNI.CheckException(Android.Base.Primitives.JNIEnvPtr)
   at FacebookLoginModule.FacebookLoginPromise.Login()
   at Fuse.UpdateListener.Invoke()
   at Fuse.UpdateManager.ProcessOnces(Fuse.Stage,Uno.Collections.List<Uno.Exception>&)
   at Fuse.UpdateManager.Update(Fuse.Stage)
   at Fuse.UpdateManager.ProcessStages()
   at Fuse.UpdateManager.Update()
   at Outracks.Simulator.Client.Application.OnUpdate()
   at Fuse.App.OnTick(object,Uno.Platform.TimerEventArgs)
   at Uno.Platform.Display.OnTick(Uno.Platform.TimerEventArgs)
   at Uno.Platform.AndroidDisplay.OnFrameCallback(double,double)

Any idea how I can fix it?

kristianhasselknippe commented 7 years ago

Hey @Dhvl-Golakiya. When you say NativeFacebookLogin; are you talking about https://www.fusetools.com/docs/native-interop/facebook-login or https://github.com/fusetools/fuse-samples/tree/master/Samples/FacebookLogin or something else ?

Dhvl-Golakiya commented 7 years ago

Hi,

I am trying this example. https://github.com/fusetools/fuse-samples/tree/feature-NativeFacebookLogin/Samples/NativeFacebookLogin

schniper commented 6 years ago

Got it working. As I'm not sure this code is maintained (or that my updates are good quality code, for that matter), I will add them here. I have encountered 2 issues: the error above, incorrect CallbackManager use, or another one, sometimes, related to FacebookSDK init not being called, although it is bound in the beginning of the class. This may be mostly a preview issue, as it seems to be related to the fact that the Lifecycle bindings have no effect, especially the Started one. So, I checked if Start has been run and if not, run it. Also, made the whole thing return an object with a token key from the login() promise, which makes it then easy to make further FB calls. For this I am actually using the JS sdk, feeding it this token.

using Fuse;
using Fuse.Platform;
using Uno;
using Uno.Compiler.ExportTargetInterop;

[extern(iOS) Require("Xcode.FrameworkDirectory", "@('FacebookSDKs-iOS':Path)")]
[extern(iOS) Require("Xcode.Framework", "@('FacebookSDKs-iOS/FBSDKCoreKit.framework':Path)")]
[extern(iOS) Require("Xcode.Framework", "@('FacebookSDKs-iOS/FBSDKLoginKit.framework':Path)")]
[extern(iOS) ForeignInclude(Language.ObjC, "FBSDKCoreKit/FBSDKCoreKit.h")]
[extern(iOS) ForeignInclude(Language.ObjC, "FBSDKLoginKit/FBSDKLoginKit.h")]
[Require("Gradle.Dependency","compile('com.facebook.android:facebook-android-sdk:4.8.+') { exclude module: 'support-v4' }")]
[Require("Gradle.Repository","mavenCentral()")]
[ForeignInclude(Language.Java, "android.content.Intent")]
[ForeignInclude(Language.Java, "com.facebook.*")]
[ForeignInclude(Language.Java, "com.facebook.appevents.AppEventsLogger")]
[ForeignInclude(Language.Java, "com.facebook.login.*")]
[ForeignInclude(Language.Java, "com.fuse.Activity")]
public class FacebookLogin
{
    public FacebookLogin()
    {
        Lifecycle.Started += Started;
        Lifecycle.EnteringInteractive += OnEnteringInteractive;
        Lifecycle.ExitedInteractive += OnExitedInteractive;
        InterApp.ReceivedURI += OnReceivedUri;
    }

    [Foreign(Language.ObjC)]
    extern(iOS) void Started(ApplicationState state)
    @{
        [[FBSDKApplicationDelegate sharedInstance]
            application: [UIApplication sharedApplication]
            didFinishLaunchingWithOptions: nil];
    @}

    extern(Android) Java.Object _callbackManager = null;
    extern(Android) bool _started = false;

    [Foreign(Language.Java)]
    extern(Android) void Started(ApplicationState state)
    @{
        @{FacebookLogin:Of(_this)._started:Set(true)};

        FacebookSdk.sdkInitialize(Activity.getRootActivity());

        @{FacebookLogin:Of(_this)._callbackManager:Set(CallbackManager.Factory.create())};

        Activity.subscribeToResults(new Activity.ResultListener()
        {
            @Override
            public boolean onResult(int requestCode, int resultCode, Intent data)
            {
                return ((CallbackManager)@{FacebookLogin:Of(_this)._callbackManager:Get()}).onActivityResult(requestCode, resultCode, data);
            }

        });
    @}

    extern(!iOS && !Android) void Started(ApplicationState state)
    {
    }

    [Foreign(Language.ObjC)]
    static extern(iOS) void OnEnteringInteractive(ApplicationState state)
    @{
        [FBSDKAppEvents activateApp];
    @}

    [Foreign(Language.Java)]
    static extern(Android) void OnEnteringInteractive(ApplicationState state)
    @{
        AppEventsLogger.activateApp(Activity.getRootActivity());
    @}

    static extern(!iOS && !Android) void OnEnteringInteractive(ApplicationState state)
    {
    }

    [Foreign(Language.Java)]
    static extern(Android) void OnExitedInteractive(ApplicationState state)
    @{
        AppEventsLogger.deactivateApp(Activity.getRootActivity());
    @}

    static extern(!Android) void OnExitedInteractive(ApplicationState state)
    {
    }

    static void OnReceivedUri(string uri)
    {
        debug_log "Received Uri: " + uri;
        if (uri.StartsWith("fb"))
        {
            OpenFacebookURL(uri);
        }
    }

    [Foreign(Language.ObjC)]
    static extern(iOS) void OpenFacebookURL(string url)
    @{
        [[FBSDKApplicationDelegate sharedInstance]
            application: [UIApplication sharedApplication]
            openURL: [NSURL URLWithString:url]
            sourceApplication: @"com.apple.mobilesafari"
            annotation: nil];
    @}

    static extern(!iOS) void OpenFacebookURL(string url)
    {
    }

    public class AccessToken
    {
        extern(iOS) string _token;
        extern(Android) string _token;
        public extern(iOS) AccessToken(string token)
        {
            _token = token;
        }
        public extern(Android) AccessToken(string token)
        {
            _token = token;
        }
        public extern(iOS) string getTokenString()
        {
            return _token;
        }
        public extern(Android) string getTokenString()
        {
            return _token;
        }
    }

    [Foreign(Language.ObjC)]
    public extern(iOS) void Login(Action<AccessToken> onSuccess, Action onCancelled, Action<string> onError)
    @{
        FBSDKLoginManager* login = [[FBSDKLoginManager alloc] init];
        [login
            logInWithReadPermissions: @[@"public_profile",]
            fromViewController: [[[UIApplication sharedApplication] keyWindow] rootViewController]
            handler: ^(FBSDKLoginManagerLoginResult* result, NSError* error)
            {
                if (error)
                {
                    onError([error localizedDescription]);
                    return;
                }
                if (result.isCancelled)
                {
                    onCancelled();
                    return;
                }
                id<UnoObject> unoAccessToken = @{AccessToken(string):New(result.token.tokenString)};
                onSuccess(unoAccessToken);
            }
        ];
    @}

    [Foreign(Language.Java)]
    [Require("Entity", "AccessToken(string)")]
    public extern(Android) void Login(Action<AccessToken> onSuccess, Action onCancelled, Action<string> onError)
    @{
        if (!@{FacebookLogin:Of(_this)._started:Get()}) {
            @{FacebookLogin:Of(_this).Started(ApplicationState):Call(@{ApplicationState.Foreground})};
        }

        LoginManager.getInstance().registerCallback((CallbackManager)@{FacebookLogin:Of(_this)._callbackManager:Get()},
            new FacebookCallback<LoginResult>()
            {
                @Override
                public void onSuccess(LoginResult loginResult)
                {
                    AccessToken accessToken = loginResult.getAccessToken();
                    UnoObject unoAccessToken = @{AccessToken(string):New(accessToken.getToken())};
                    onSuccess.run(unoAccessToken);
                }

                @Override
                public void onCancel()
                {
                    onCancelled.run();
                }

                @Override
                public void onError(FacebookException exception)
                {
                    onError.run(exception.toString());
                }
            }
        );
        LoginManager.getInstance().logInWithReadPermissions(Activity.getRootActivity(), java.util.Arrays.asList("public_profile"));
    @}
}
using Fuse.Scripting;
using Uno.Permissions;
using Uno.Threading;
using Uno.UX;
using Uno;

[UXGlobalModule]
public class FacebookLoginModule : NativeModule
{
    class FacebookLoginPromise : Promise<FacebookLogin.AccessToken>
    {
        readonly FacebookLogin _facebookLogin;

        public FacebookLoginPromise(FacebookLogin facebookLogin)
        {
            _facebookLogin = facebookLogin;
            if defined(Android)
            {
                Permissions.Request(Permissions.Android.INTERNET).Then(
                    OnPermissionsPermitted,
                    OnPermissionsRejected);
            }
            else
            {
                Fuse.UpdateManager.AddOnceAction(Login);
            }
        }

        void Login()
        {
            if defined(iOS || Android)
                _facebookLogin.Login(this.Resolve, OnCancelled, OnError);
            else
                throw new NotImplementedException();
        }

        void OnCancelled()
        {
            Reject(new Exception("Cancelled"));
        }

        void OnError(string error)
        {
            Reject(new Exception(error));
        }

        extern(Android) void OnPermissionsPermitted(PlatformPermission p)
        {
            Fuse.UpdateManager.AddOnceAction(Login);
        }

        extern(Android) void OnPermissionsRejected(Exception e)
        {
            Reject(e);
        }
    }

    static readonly FacebookLoginModule _instance;
    readonly FacebookLogin _facebookLogin;

    public FacebookLoginModule()
    {
        if (_instance != null)
            return;

        _facebookLogin = new FacebookLogin();

        _instance = this;
        Resource.SetGlobalKey(_instance, "FacebookLogin");
        AddMember(new NativePromise<FacebookLogin.AccessToken, Fuse.Scripting.Object>("login", Login, Converter));
    }

    Future<FacebookLogin.AccessToken> Login(object[] args)
    {
        return new FacebookLoginPromise(_facebookLogin);
    }

    static Fuse.Scripting.Object Converter(Context context, FacebookLogin.AccessToken accessToken)
    {
        var wrapperObject = context.NewObject();
        wrapperObject["token"] = accessToken.getTokenString();
        return wrapperObject;
    }
}
FacebookLogin.login()
            .then(result => {
                fetchFbData(result.token)
            })
            .catch(error => {
                console.log('Login failed: ' + error)
            })
gtoto007 commented 6 years ago

@schniper thank you for your fix. It's works.

I have fix FacebookLoginModule class because I have an error on method Converter when I launched project on Desktop.

using Fuse.Scripting;
using Uno.Permissions;
using Uno.Threading;
using Uno.UX;
using Uno;

[UXGlobalModule]
public class FacebookLoginModule : NativeModule
{
    class FacebookLoginPromise : Promise<FacebookLogin.AccessToken>
    {
        readonly FacebookLogin _facebookLogin;

        public FacebookLoginPromise(FacebookLogin facebookLogin)
        {
            _facebookLogin = facebookLogin;
            if defined(Android)
            {
                Permissions.Request(Permissions.Android.INTERNET).Then(
                    OnPermissionsPermitted,
                    OnPermissionsRejected);
            }
            else
            {
                Fuse.UpdateManager.AddOnceAction(Login);
            }
        }

        void Login()
        {
            if defined(iOS || Android)
                _facebookLogin.Login(this.Resolve, OnCancelled, OnError);
            else
                throw new NotImplementedException();
        }

        void OnCancelled()
        {
            Reject(new Exception("Cancelled"));
        }

        void OnError(string error)
        {
            Reject(new Exception(error));
        }

        extern(Android) void OnPermissionsPermitted(PlatformPermission p)
        {
            Fuse.UpdateManager.AddOnceAction(Login);
        }

        extern(Android) void OnPermissionsRejected(Exception e)
        {
            Reject(e);
        }
    }

    static readonly FacebookLoginModule _instance;
    readonly FacebookLogin _facebookLogin;

    public FacebookLoginModule()
    {
        if (_instance != null)
            return;

        _facebookLogin = new FacebookLogin();

        _instance = this;
        Resource.SetGlobalKey(_instance, "FacebookLogin");
        AddMember(new NativePromise<FacebookLogin.AccessToken, Fuse.Scripting.Object>("login", Login, Converter));
    }

    Future<FacebookLogin.AccessToken> Login(object[] args)
    {
        return new FacebookLoginPromise(_facebookLogin);
    }

    static Fuse.Scripting.Object Converter(Context context, FacebookLogin.AccessToken accessToken)
    {
        var wrapperObject = context.NewObject();
        if defined(iOS || Android)
                 wrapperObject["token"] = accessToken.getTokenString();
                return wrapperObject;
    }
}
schniper commented 6 years ago

I know there was an issue, in the end my token class looked like this:

public class AccessToken
    {
        string _token;

        public AccessToken(string token)
        {
            _token = token;
        }

        public string getTokenString()
        {
            return _token;
        }
    }

No native stuff. Of course, this works because for me it's enough to get the token so I can play with the JS API further, not use the native SDK.

If you use this update you don't have to change the other stuff.