Geolocation

Quick backstory

 You are mobile experts, I at least expected the Geolocation to work

An unsatisfied client

Yann LEFLOUR

Deputy CTO chez

How to implement a perfect geolocation service...

... and 5 things to learn from it

Back to basics

  • The user presses a button
  • We display his position
export function getCurrentPosition(timeout, enableHighAccuracy, maximumAge) {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject, {
      timeout,
      enableHighAccuracy,
      maximumAge,
    });
  });
}

Step 1 - Promisify

export default class App extends Component<Props> {
  state = {
    location: '',
    isLoading: false,
  };

  localize = () => {};

  render() {
    if (this.state.isLoading)
      return (
        <View style={styles.container}>
          <ActivityIndicator size="large" />
        </View>
      );

    return (
      <View style={styles.container}>
        <Button title="Localize" onPress={this.localize} />
        <Text>{this.state.location}</Text>
      </View>
    );
  }
}

Step 2 - Setup component

localize = () => {
    this.setState({ isLoading: true, error: false });
    getCurrentPosition(5000)
      .then(location => {
        this.setState({
          isLoading: false,
          location: JSON.stringify(location, null, 2),
        });
      });
  };
};

Step 3 - Handle query

The result?

Come on! Why won't you work!

A typical phone user with geolocation service disabled

1st rule

  • Inform your user of errors happening

Context:

  • The location service is disabled
export function getCurrentPosition(timeout, enableHighAccuracy, maximumAge) {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject, {...})
      .catch(error => {
        switch (error.code) {
          case 1:
            return Promise.reject(
              new Error(ERROR_MESSAGES.GEOLOCATION_PERMISSION_DENIED)
            );
          case 2:
            return Promise.reject(new Error(ERROR_MESSAGES.GEOLOCATION_INACTIVE));
          case 3:
            return Promise.reject(new Error(ERROR_MESSAGES.GEOLOCATION_TIMEOUT));
          default:
            return Promise.reject(error);
      }
    });
  });
}

Step 1 - Normalize Errors

localize = () => {
  /* ... */
  getCurrentPosition()
    .then(...)
    .catch(error => {
      let errorMessage = '';

      switch (error.message) {
        case ERROR_MESSAGES.GEOLOCATION_INACTIVE:
          errorMessage = 'Geolocation service disabled';
      }

      this.setState({
        isLoading: false,
        errorMessage: errorMessage,
      });
    });
};

Step 2 - Translate Error

render() {
  if (this.state.isLoading) return (...);

  if (this.state.errorMessage)
    return (
      <View style={styles.container}>
        <Button title="Display Position" onPress={this.localize} />
        <Text>{`Error: ${this.state.errorMessage}`}</Text>
      </View>
    );

  return (...);
}

Step 3 - Display it

The result?

Dafuk is the location service ?!

A typical phone user with geolocation service disabled

2nd rule

  • Help your user handle the error

Context:

  • The location service is disabled
  • The user is not a developer

How to activate the location service on iOS

The pragmatic solution

The result?

Guess it's faster to uninstall the app

A typical phone user who has to activate the location service manually

3rd rule

  • Help resolving the error as much as you can

Context:

  • The location service is disabled
  • The user is not a developer
  • The user is lazy
export function openIosLocationSettings() {
  return new Promise((resolve, reject) => {
    return Linking.openURL('App-Prefs:root=LOCATION_SERVICES')
      .then(() => {
        const resolveOnChange = state => {
          if (state === 'active') {
            AppState.removeEventListener('change', resolveOnChange);
            return resolve();
          }
        };
        AppState.addEventListener('change', resolveOnChange);
      })
      .catch(() => {
        reject(new Error(ERROR_MESSAGES.GEOLOCATION_OPEN_SETTINGS_FAILED));
      });
  });
}

Step 1 - Create service to open location settings

// App.js

displayLocationServiceDisabledAlert = () => {
  return new Promise((resolve, reject) => {
    Alert.alert(
      'Location service disabled',
      'Please activate it in order to display your position',
      [
        {
          text: 'Later',
          onPress: () => {
            reject(new Error(ERROR_MESSAGES.GEOLOCATION_INACTIVE));
          },
        },
        {
          text: 'Activate',
          onPress: () => {
            resolve(openLocationSettings());
          },
        },
      ]
    );
  });
};

Step 2 - Add alert on error

// App.js
localize = () => {
  this.setState({ isLoading: true, error: false });
  getCurrentPosition()
    .catch(error => {
      switch (error.message) {
        case ERROR_MESSAGES.GEOLOCATION_INACTIVE:
          return this.displayLocationServiceDisabledAlert()
            .then(this.localize);
      }
    })
    .then(...) // Preserve success message display
    .catch(...); // Preserve error message display
};

Step 3 - Display alert on error

The result?

I'm getting really tired with this...

A typical phone user who denied location access to your app

New 1st rule

  • Plan out your error flow beforehand

The geolocation error flow

The result?

The result?

The 4 takeaways

  • Plan your error flow beforehand
  • Inform your user of errors happening
  • Tell them how to handle them
  • Facilitate any error handling action you can
  • Don't waste time                                         

React Native Geolocation Suite

yarn add @bam.tech/react-native-geolocation-suite
# OR
npm install @bam.tech/react-native-geolocation-suite

Questions?

Geolocation

By Yann Leflour

Geolocation

  • 1,006