Innim / flutter_login_facebook

Flutter Plugin to login via Facebook.
BSD 3-Clause "New" or "Revised" License
71 stars 53 forks source link

accessToken and logIn methods hang the app (they never return) on iOS #111

Open jakubkrapiec opened 1 month ago

jakubkrapiec commented 1 month ago

Hi, me and my company are having a release blocked by a bizarre bug. On iOS only (Android works fine), awaiting for accessToken and the logIn method hangs the app, as the Futures never complete. They don't throw or complete with an error, they just never complete. This behavior started as we switched to the 2.0.0 version of the plugin. The ATT permission is granted. Below is the code responsible for starting the Facebook sign in flow after the user taps a button on the log in screen. I'd be grateful for any help!

  Future<bool> _signInWithFacebook() async {
    if (mounted) {
      setState(() {
        _preloadersSet.add(Preloaders.facebookSignIn);
      });
    }
    await AppTrackingTransparency.requestTrackingAuthorization();
    try {
      final fb = FacebookLogin(debug: kDebugMode);
      var token = await fb.accessToken.timeout(
        const Duration(seconds: 3),
        onTimeout: () async {
          await inject<SessionService>().forget();
          return null;
        },
      );
      if (token == null) {
        if (io.Platform.isAndroid) {
          final expressResult = await fb.expressLogin();
          if (expressResult.status == FacebookLoginStatus.success) {
            token = expressResult.accessToken;
          }
        }
        for (var tryNumber = 0; tryNumber < 2; tryNumber++) {
          if (token == null) {
            final result = await fb.logIn(permissions: [FacebookPermission.publicProfile, FacebookPermission.email]);
            // ignore: prefer-switch-with-enums
            if (result.status == FacebookLoginStatus.success) {
              token = result.accessToken;
            } else if (result.status == FacebookLoginStatus.cancel) {
              break;
            }
          } else {
            break;
          }
        }
        if (!mounted) return false;
        if (token == null) {
          throw Exception(context.loc.couldNotLogInWithFacebook);
        }
      }
      final result = await _authenticationService.authenticate(
        await _sessionService.authenticateBySocial(AuthenticateBySocialDTO(
          id: token.userId,
          provider: SocialProvider.facebook,
          token: token.isLimitedLogin ? token.authenticationToken : token.token,
        )),
      );
      if (mounted) {
        setState(() {
          _preloadersSet.remove(Preloaders.facebookSignIn);
        });
      }
      if (result == true &&
          !await _pushNotificationService.isOpenedFromPushNotificationWithUrl() &&
          _navigationService.isCurrent(Routes.login)) {
        inject<AnalyticsService>().logEvent(AnalyticsEvents.landingPageAuthChosen, {'auth': 'fb'});
        _navigate();
      }
      return result;
    } catch (error) {
      if (error is HttpException && (error.message?.contains('email') ?? false)) {
        inject<NotifyService>().error(error.message!);
      } else {
        inject<NotifyService>().error(error);
      }
      if (mounted) {
        setState(() {
          _preloadersSet.remove(Preloaders.facebookSignIn);
        });
      }
      return false;
    }
  }
jakubkrapiec commented 1 month ago

In XCode console, I see such warnings, but not when trying to trigger FB Sign In, but on the startup of the app:

nw_resolver_start_query_timer_block_invoke [C36.1.1] Query fired: did not receive all answers in time for ep1.facebook.com:443

tcp_input [C35.1.1.1:3] flags=[R] seq=3279027870, ack=0, win=0 state=LAST_ACK rcv_nxt=3279027870, snd_una=3454966212

greymag commented 1 month ago

Hello!

I'm sorry, I somehow missed that issue. Did you get it sorted out?

In the above code, does the first call to fb.accessToken cause hangs or just the fb.logIn() call?

jakubkrapiec commented 1 month ago

Hi, thanks for reaching out! I've managed to debug this problem and found out that in the plugin, in this method in file facebook_plugin_channel.dart:

invokeMethodNow<bool>(PluginMethod.isReady).then((value) {
      if (value == true) _ready();
    });

the value is always false. I've fixed this by forking your repo, and changing the code to this:

invokeMethodNow<bool>(PluginMethod.isReady).then((value) {
      //if (value == true) _ready();
      _ready();
    });

Me and my team have tested this fix and now we use it in prod, without issues. Hope it helps you with debugging. In case of any questions, I'd be happy to help.

jakubkrapiec commented 1 month ago

And to answer your question: both fb.accessToken and fb.logIn() hang the app.

marceloamx commented 3 weeks ago

Hi, I'm having the same issue., Is there any other solution apart from the one suggested by jakubkrapiec? I tested and it works but I was trying to avoid the fork.

I will be grateful for any help!

greymag commented 2 weeks ago

Hello @marceloamx

The problem is that in the latest versions of FB SDK, the data is not available immediately on startup, but only after time. So if you try to check the current accessToken, it will be null until the initialization is complete.

As a workaround, I added the logic above, the one with "isReady". Hang of the app is a result of some logic error, because apparently the initialization complete handler doesn't work properly. It could be my mistake, or buggy FB SDK implementation, or even some random conflict with another package in your project.

Fork doesn't solve the problem per se. It fixed the hang, but it brought back the problem with uninitialised data. But if it doesn't matter for a particular implementation, it will work fine.

I'm going to look in the issue closely and add some fallback logic with timer, to exclude the hang behaviour.

Sorry for the late reply, I have some shortage of time. But I will try to fix this issue as soon as possible.