Agontuk / react-native-geolocation-service

React native geolocation service for iOS and android
https://www.npmjs.com/package/react-native-geolocation-service
MIT License
1.61k stars 292 forks source link

When the user grants the permission, the AppState changes #390

Open ovsiannykov opened 1 year ago

ovsiannykov commented 1 year ago

Platforms

Versions

Please add the used versions/branches

Description

When the user grants permission to use geolocation he AppState changes. Everything would be fine, but I have a hook that monitors the exit of the application from sleep, and makes a request to update the token, user, and so on. Because of this, the reload effect is obtained and the user is thrown to the main screen

Code

My useLocation Hook:

import { useContext, useEffect, useState } from 'react';
import Geolocation, { GeoCoordinates } from 'react-native-geolocation-service';

import { localesContext } from '../../../shared/locale/locale.provider';
import { errorContext } from './../../../core/error/error.provider';

import { locationPermission } from '../../helpers/location-permissions';

const useLocation = (): Geolocation.GeoCoordinates | null => {
  const [location, setLocation] = useState<GeoCoordinates | null>(null);
  const { i18n } = useContext(localesContext);
  const { bug } = useContext(errorContext);

  const getLocation = async (): Promise<void> => {
    const hasPermission = await locationPermission(i18n, bug);

    if (!hasPermission) {
      return;
    }

    Geolocation.getCurrentPosition(
      position => {
        setLocation(position.coords);
      },
      error => {
        bug(t(i18n)`Code ` + error.code + t(i18n)`, please try again`);
        setLocation(null);
      },
      {
        accuracy: {
          android: 'high',
          ios: 'best',
        },
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
        distanceFilter: 0,
        forceRequestLocation: true,
        forceLocationManager: false,
        showLocationDialog: true,
      },
    );
  };

  useEffect(() => {
    getLocation();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return location;
};

export default useLocation;

My location permissions helper:

import { t } from '@lingui/macro';
import { Alert, Linking, PermissionsAndroid, Platform } from 'react-native';
import Geolocation from 'react-native-geolocation-service';

import { IS_ANDROID, IS_IOS } from '../constants/theme';

export const locationPermission = async (i18n: I18n, bug: (rawError: unknown) => void) => {
  const hasPermissionIOS = async () => {
    const openSetting = () => {
      Linking.openSettings().catch(() => {
        bug(t(i18n)`Unable to open settings, please try again`);
      });
    };

    try {
      const status = await Geolocation.requestAuthorization('whenInUse');

      if (status === 'granted') {
        return true;
      }

      if (status === 'denied') {
        bug(t(i18n)`Location permission denied, please try again`);
      }

      if (status === 'disabled') {
        Alert.alert(
          t(i18n)`Allow “Workfly” To Access your Location?`,
          'Workfly helps you to check you current location',
          [{ text: t(i18n)`Go to Settings`, onPress: openSetting }],
        );
      }
      return false;
    } catch (error) {
      bug(error);
      return false;
    }
  };

  if (IS_IOS) {
    const hasPermission = await hasPermissionIOS();
    return hasPermission;
  }

  if (IS_ANDROID && Platform.Version < 23) {
    return true;
  }

  const hasPermission = await PermissionsAndroid.check(
    PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
  );

  if (hasPermission) {
    return true;
  }

  const status = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
  );

  if (status === PermissionsAndroid.RESULTS.GRANTED) {
    return true;
  }

  if (status === PermissionsAndroid.RESULTS.DENIED) {
    bug(t(i18n)`Opps... Location permission denied, please enable permissions`);
  } else if (status === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
    bug(t(i18n)`Ooops... Location permission revoked, please enable permissions`);
  }

  return false;
};

My useWakeUp Hook:

import { AppState } from 'react-native';

const useWakeUp = (myFunc: () => ReactNode | Promise<void> | void) => {
  useEffect(() => {
    const subscription = AppState.addEventListener('change', nextAppState => {
      if (nextAppState === 'active') {
        myFunc();
      }
    });

    return () => subscription.remove();
  }, [myFunc]);
};

export default useWakeUp;

My App.tsx

import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { en, he } from 'make-plural/plurals';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { Linking, LogBox, StatusBar } from 'react-native';
import RNBootSplash from 'react-native-bootsplash';
import 'react-native-gesture-handler';

import AppProvider, { appContext } from '../core/app/app.provider';
import { ErrorProvider, errorContext } from '../core/error/error.provider';
import { SessionProvider } from '../core/session/session.provider';
import CompanyProvider from '../shared/entities/company/company.provider';
import EntryProvider from '../shared/entities/entry/entry.provider';
import UserProvider from '../shared/entities/user/user.provider';
import { LocalesProvider } from '../shared/locale/locale.provider';

import { messages as transEn } from '../locales/en/messages';
import { messages as transHe } from '../locales/he/messages';
import FolderProvider from '../shared/entities/folder/folder-provider';
import TaskProvider from '../shared/entities/task/task-provider';
import DepartmentsProvider from '../shared/selects/departments/departments-provider';
import FoldersProvider from '../shared/selects/folders/folders-provider';
import UsersProvider from '../shared/selects/users/users-provider';
import LoadingIndicator from './components/loading-indicator/loading-indicator';
import Toaster from './components/toaster/toaster';
import { COLORS } from './constants/theme';
import { LINKING_CONFIG } from './helpers/linking-config';
import AuthNavigation from './navigation/auth-navigation/auth-navigation';
import RootNavigator from './navigation/root-navigator/root-navigator';
import LoadingScreen from './screens/other/loading-screen/loading-screen';

import useWakeUp from './hooks/use-wake-up/use-wake-up';

const translationsMessages = { en: transEn, he: transHe };

// ignore yellow warning debug messages
LogBox.ignoreAllLogs();

function App() {
  const [loading, setLoading] = useState(false);
  const { setStore, store } = useContext(appContext);
  const { bug } = useContext(errorContext);
  const navigation = useNavigation();

  useEffect(() => {
    const init = async () => {
      // …do multiple sync or async tasks
    };
    init().finally(async () => {
      await RNBootSplash.hide({ fade: true });
    });
  }, []);

  const getSession = useCallback(async () => {
    setLoading(true);
    try {
      const storageToken = await SessionProvider.getSessionCookie();
      if (storageToken) {
        const [session] = await SessionProvider.getSession(storageToken);
        setStore('user', session.user);
      }
    } catch (e) {
      console.log(`APP getSession error: ${e}`);
      bug(e);
    } finally {
      setLoading(false);
    }
  }, [bug, setStore]);

  useWakeUp(getSession);

  const linkingListener = useCallback(async () => {
    try {
      const url = await Linking.getInitialURL();
      if (url) {
        console.log(url);
        if (!store.user) {
          //@ts-ignore
          navigation.navigate('ViaLink');
        }
      }
    } catch (error) {
      console.log(`Link Problem: ${error}`);
      bug(error);
    }
  }, [bug, navigation, store.user]);

  useEffect(() => {
    getSession();
  }, [getSession]);

  useEffect(() => {
    linkingListener();
  }, [linkingListener]);

  if (loading) {
    return <LoadingScreen color={COLORS.primary} />;
  }

  return (
    <>
      <CompanyProvider>
        <EntryProvider>
          <FolderProvider>
            <FoldersProvider>
              <TaskProvider>
                <DepartmentsProvider>
                  <UsersProvider>
                    <UserProvider>
                      {store.user ? <RootNavigator /> : <AuthNavigation />}
                    </UserProvider>
                  </UsersProvider>
                </DepartmentsProvider>
              </TaskProvider>
            </FoldersProvider>
          </FolderProvider>
        </EntryProvider>
      </CompanyProvider>
      <Toaster />
    </>
  );
}

function Index() {
  const i18n = setupI18n({
    messages: translationsMessages,
    locale: 'en',
    localeData: {
      en: { plurals: en },
      he: { plurals: he },
    },
  });

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <LocalesProvider i18n={i18n}>
        <AppProvider store={{}} techStore={{ session: null }}>
          <ErrorProvider error={null}>
            <NavigationContainer
              // @ts-ignore
              linking={LINKING_CONFIG}
              fallback={<LoadingIndicator color={COLORS.primary} size="large" />}
            >
              <App />
            </NavigationContainer>
          </ErrorProvider>
        </AppProvider>
      </LocalesProvider>
    </>
  );
}

export default Index;
alainib commented 1 year ago

Geolocation.requestAuthorization is IOS ONLY !