[Android] Facebook login - need help with reproducing / debugging

Short introduction:

Facebook signing in is failing after clicking “login” on facebook modal. The problem is everything works fine:

  • in Expo client Android/iOS
  • on iOS (production build from AppStore)
  • on Android emulator (signed apk)

Does not work:

  • on physical Android device, apk downloaded from Google Play

I don’t know how to solve this if I don’t have any stacktrace. I added error tracking (Sentry.io), but I don’t get any data when the login process is failing, but tracking works fine.

Environment

[18:08:47] Generating diagnostics report...
[18:08:47] You can join our slack here: https://slack.expo.io/.

  Expo CLI 2.2.4 environment info:
    System:
      OS: macOS 10.14.3
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 11.3.0 - /usr/local/bin/node
      Yarn: 1.12.3 - /usr/local/bin/yarn
      npm: 6.4.1 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    IDEs:
      Xcode: 10.1/10B61 - /usr/bin/xcodebuild
    npmPackages:
      expo: ^31.0.4 => 31.0.6 
      react: 16.5.0 => 16.5.0 
      react-native: https://github.com/expo/react-native/archive/sdk-31.0.1.tar.gz => 0.57.1 
      react-navigation: ^2.18.2 => 2.18.3 
    npmGlobalPackages:
      expo-cli: 2.2.4

  Diagnostics report:
    https://exp-xde-diagnostics.s3.amazonaws.com/revcode.it-e04eefd8-88a5-4b5d-8ad2-5b22bc6899cb.tar.gz

App.json

{
  "expo": {
    "name": "<app_display_name>",
    "description": "Production",
    "slug": "<app_name>",
    "privacy": "unlisted",
    "sdkVersion": "31.0.0",
    "platforms": [
      "ios",
      "android"
    ],
    "version": "1.0.13",
    "orientation": "portrait",
    "facebookScheme": "<fb_scheme>",
    "facebookAppId": "<app_id>",
    "facebookDisplayName": "<app_display_name>",
    "scheme": "<app_name>",
    "icon": "./assets/images/icon.png",
    "splash": {
      "image": "./assets/images/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": false,
      "bundleIdentifier": "<bundle>",
      "infoPlist": {
        "LSApplicationQueriesSchemes": [
          "<app_name>"
        ],
        "NSPhotoLibraryUsageDescription": "Upload photo for services to attract potential attendees"
      }
    },
    "android": {
      "permissions": [], << Needed to publish on Google Play without warning about SMS warnings
      "versionCode": 11,
      "package": "<package>",
      "config": {
        "googleMaps": {
          "apiKey": "<key>"
        }
      }
    },
    "hooks": {
      ...
  }
}

Code:

// @flow
import React from 'react';
import {Alert, AsyncStorage, Image, Linking, ScrollView, StyleSheet, Switch, Text, View} from "react-native";
import Button from "../components/ui/Button";
import ButtonStyles from "../constants/ButtonStyles";
import {backgroundColor, brandSecondary, linkColor, text300, text500Default, themePrimary} from "../constants/Colors";
import {Font} from "../constants/TextStyles";
import {storageTokenKey} from "../constants/Common";
import {MainRoute} from "../navigation/AppNavigator";
import type {User} from "../api/authApi";
import {authenticate, fetchMe} from "../api/authApi";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
import {loginSuccess} from "../redux/action/accountActions";
import {fetchSystemStatus, SystemHealth} from "../api/utilApi";
import _ from "lodash";
import {PRIVACY_POLICY_URL, TOS_URL} from "../constants/GDPRUrls";

type Props = {
  loginSuccess: (user: User) => void,
  navigation: any
}

type State = {
  isLoading: boolean,
  isGDPRChecked: boolean,
  systemStatus: string
}

type FacebookProfile = {
  id: number,
  email: string,
  name: string
}

const APP_ID = '<my_facebook_id>';
const FACEBOOK_OPTIONS = {
  permissions: ['public_profile', 'email'],
};

class LoginScreen extends React.Component<Props, State> {
  static navigationOptions = {
    header: null,
  };

  constructor(props) {
    super(props);
    this.state = {
      isLoading: false,
      isGDPRChecked: false,
      systemStatus: SystemHealth.UNKNOWN
    }
  }

  openApplication = (): void => this.props.navigation.navigate(MainRoute.APPLICATION);

  loginUser = async (): Promise<void> => {
    return fetchMe()
      .then(response => response.data)
      .then(this.props.loginSuccess);
  };

  fetchFacebookProfile = async (accessToken: string): Promise<FacebookProfile> => {
    return fetch(`https://graph.facebook.com/me?access_token=${accessToken}&fields=id,name,email`)
      .then(response => response.json());
  };

  openFacebookLogin = async (): Promise<string> => {
    const {type, token} = await Expo.Facebook.logInWithReadPermissionsAsync(APP_ID, FACEBOOK_OPTIONS);
    if (type === 'success') {
      return token;
    }

    if (type === 'cancel') {
      throw Error("Login canceled");
    }

    throw Error("Unknown error");
  };

  async componentDidMount(): Promise<void> {
    this.setState({isLoading: false});

    const systemStatus = await fetchSystemStatus();
    console.log("System:", systemStatus);
    this.setState({systemStatus});

    if (systemStatus !== SystemHealth.UP) {
      this.showMessageAboutSystemHealth()
    }
  }

  tryAuthenticate = async () => {
    let email: string = "";
    let facebookAccessToken: string = "";
    try {
      facebookAccessToken = await this.openFacebookLogin();
      this.setState({isLoading: true});
      const facebookProfile = await this.fetchFacebookProfile(facebookAccessToken);
      email = facebookProfile.email;
      console.log("Facebook email:", email);
    } catch (error) {
      console.error(error);
      const message = _.get(error, 'message', 'No error message.');
      throw Error(message + " Please try again later. Code: 102");
    }

    try {
      const apiToken = await authenticate(email, facebookAccessToken);
      await AsyncStorage.setItem(storageTokenKey, apiToken);
      console.log("API token:", apiToken);
    } catch (error) {
      console.error(error);
      const message = _.get(error, 'response', 'No error message.');
      throw Error(message + " Please try again later. Code: 101");
    }

    try {
      await this.loginUser();
      console.log("Login success");
    } catch (error) {
      console.error(error);
      const message = _.get(error, 'response.msg', 'No error message.');
      throw Error(message + " Please try again later. Code: 100");
    }
  };

  onFacebookLoginPress = async (): Promise<void> => {
    try {
      await this.tryAuthenticate();
      this.openApplication();
    } catch (error) {
      this.setState({isLoading: false});
      Alert.alert("Login error", error.message);
    }
  };

  openLink = (url) => {
    Linking.canOpenURL(url)
      .then(supported => {
        if (supported) {
          Linking.openURL(url);
        }
      });
  };

  onTermsOfServicesPress = () => this.openLink(TOS_URL);
  onPrivacyPolicyPress = () => this.openLink(PRIVACY_POLICY_URL);
  showMessageAboutSystemHealth = (): void => Alert.alert("System unavailable", "System right now is unavailable. Please try again in few minutes.");
  onDisabledButtonPress = (): void => this.showMessageAboutSystemHealth();

  render() {
    const {isLoading, systemStatus, isGDPRChecked} = this.state;

    const isSystemUp = !(systemStatus !== SystemHealth.UP);
    return (
      <ScrollView>
        <View style={styles.container}>

          <View
            style={styles.headerContainer}
          >
            <Text style={[styles.title, {
              color: text500Default
            }
            ]}>
              Some
            </Text>
            <Text style={[styles.title, {
              color: brandSecondary
            }
            ]}>
              App
            </Text>
          </View>

          <Image
            source={require('../assets/images/login-image.jpeg')}
            style={styles.image}
          />
          <Text style={styles.text}>some text
          </Text>

          <Button
            onPress={this.onFacebookLoginPress}
            style={ButtonStyles.primaryRound}
            onPressDisabled={this.onDisabledButtonPress}
            disabled={!isGDPRChecked}
            loading={isLoading}
          >
            Sign in with Facebook
          </Button>
          <View
            style={{
              flexDirection: 'row',
              justifyContent: 'space-between',
              marginBottom: 10
            }}
          >
            <Button
              style={[ButtonStyles.linkSmall]}
              onPress={this.onTermsOfServicesPress}
              textStyle={{
                color: linkColor,
                fontSize: 14
              }}>
              Terms of services
            </Button>

            <Button
              style={[ButtonStyles.linkSmall]}
              onPress={this.onPrivacyPolicyPress}
              textStyle={{
                color: linkColor,
                fontSize: 14
              }}>
              Privacy policy
            </Button>
          </View>
          <View
            style={{
              justifyContent: 'center',
              alignItems: 'center',
              paddingHorizontal: 25,
            }}
          >
            <View
              style={{
                flexDirection: 'row',
                alignItems: 'center',
              }}>
              <Switch
                value={isGDPRChecked && isSystemUp}
                onValueChange={(isGDPRChecked) => this.setState({isGDPRChecked})}
              >
              </Switch>
              <Text
                style={styles.agreementText}
              >
                I hereby agree to have my personal data processed for the purposes of realization of the portal’s
                services.
              </Text>
            </View>
          </View>
        </View></ScrollView>
    );
  }
}

const mapStateToProps = state => ({
  account: state.account,
});
const mapDispatchToProps = dispatch => (bindActionCreators(
  {loginSuccess},
  dispatch,
));
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 40,
    justifyContent: 'space-around',
    backgroundColor: backgroundColor,
    alignItems: 'center'
  },
  agreementText: {
    fontSize: 12,
    color: text300,
    marginLeft: 10
  },
  agreementLink: {
    fontSize: 12,
    color: linkColor,
  },
  title: {
    fontSize: 40,
    textAlign: 'center',
    fontFamily: Font.avenirNextDemiBold
  },
  text: {
    color: themePrimary,
    fontSize: 16,
    fontFamily: Font.avenirNextMedium,
    textAlign: 'justify',
    marginBottom: 40,
  },
  image: {
    width: 180,
    height: 180,
    borderRadius: 90,
  },
  headerContainer: {
    flexDirection: 'row'
  }
});

Hey @revcode.it,

Can you try connecting your physical device and enabling USB debugging, loading up your signed APK and running adb logcat in Android Studio. Device logs tend to shed a lot more light on a situation.

Cheers,
Adam

After hours of fighting I find out. Hint and solution: Standalone app different hash from fetch:android:hashes :slight_smile:

2 Likes

Sweet. Glad you got to the bottom of it. I admire the perseverance.

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.