react-native-background-geolocation icon indicating copy to clipboard operation
react-native-background-geolocation copied to clipboard

ConcurrentModificationException java.util.ArrayList$Itr in next

Open macielrsf opened this issue 2 years ago • 15 comments

Your Environment

  • Plugin version: 4.10.0
  • Platform: Android
  • OS version: macOS 13.2.1
  • Device manufacturer / model: Mac mini (Chip Apple M1)
  • React Native version (react-native -v): 0.71.2
  • Plugin config
        const config = {
            notification: {
                title: 'Company',
                text: 'Executando',
            },
            disableMotionActivityUpdates: true,
            desiredAccuracy: Platform.select({
                ios: BackgroundGeolocation.DESIRED_ACCURACY_NAVIGATION,
                android: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
            }),
            debug: __DEV__ ? true : false,
            logLevel: __DEV__
                ? BackgroundGeolocation.LOG_LEVEL_VERBOSE
                : BackgroundGeolocation.LOG_LEVEL_OFF,
            stopOnTerminate: false,
            startOnBoot: false,
            url,
            batchSync: true,
            autoSync: true,
            autoSyncThreshold: 5,
            maxBatchSize: 100,
            distanceFilter: Platform.select({ios: 10, android: 20}),
            stopTimeout: 5,
            locationTemplate:
                '{"latitude":<%= latitude %>,"longitude":<%= longitude %>,"velocidade":<%= speed %>,"time":"<%= timestamp %>","id":"<%= uuid %>","nivel_bateria": <%= battery.level %>}',
            headers: {
                accept: 'application/json',
                'content-type': 'application/json',
                authorization: httpClient.defaults.headers.common.Authorization,
            },
            params: {
                timezone: 'America/Recife',
            },
            extras: {
                modelo_dispositivo,
                fabricante,
            },
            //iOS Options
            showsBackgroundLocationIndicator: true,
            stationaryRadius: 10,
            heartbeatInterval: Platform.select({ios: 10, android: 60}),
            preventSuspend: Platform.select({ios: true, android: false}),
            activityType:
                BackgroundGeolocation.ACTIVITY_TYPE_AUTOMOTIVE_NAVIGATION,
        };

Expected Behavior

None bug reported on Sentry

Actual Behavior

Native Exception (Android): ConcurrentModificationException java.util.ArrayList$Itr in next

Stack Trace: java.util.ConcurrentModificationException: null at java.util.ArrayList$Itr.next(ArrayList.java:860) at com.transistorsoft.locationmanager.util.c$j.onPermissionGranted at com.intentfilter.androidpermissions.PermissionHandler.checkPermissions(PermissionHandler.java:44) at com.intentfilter.androidpermissions.PermissionManager.checkPermissions(PermissionManager.java:55) at com.transistorsoft.locationmanager.util.c$j.run at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:236) at android.app.ActivityThread.main(ActivityThread.java:7912) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)

Context

Apparently the crash happens after our users have accepted background geolocation permissions. On Sentry, the most reports are from Android 10 and 11 in Xiaomi devices mainly (Redmi Note 8, Redmi Note 9, Redmi Prime 9S, Redmi Note 9S, Redmi Note 11, etc)

Complete Issue link: https://hubsoftbrasil.sentry.io/share/issue/ae5d1d147c7241eea763fd7b9643fa0a/

macielrsf avatar Mar 07 '23 19:03 macielrsf

@christocracy I would like your help with it. Do you need any other info ?

macielrsf avatar Mar 17 '23 13:03 macielrsf

I do not have any explanation for this error. The plugin code is properly interacting with a static List within synchronized blocks.

Show me your code.

christocracy avatar Mar 17 '23 14:03 christocracy

Below, it's my component where I control the "start/stop" of your service:

import React, {useCallback, Fragment, useEffect, useState} from 'react';

import DeviceInfo from 'react-native-device-info';
import NetInfo from '@react-native-community/netinfo';
import {connect} from 'react-redux';
import BackgroundGeolocation from 'react-native-background-geolocation';
import moment from 'moment';
import {useDispatch} from 'react-redux';
import {AppState, Platform} from 'react-native';

import showToast from '~/services/Toast';
import Unauthorized from '~/services/Unauthorized';

import httpClient from '~/HttpClient';

import * as types from '~/constants/ActionTypes';

import {
    modificaNivelBateria,
    modificaDispositivoInfo,
    modificaStatusConexao,
} from '~/actions/DeviceInfoActions';

import useSyncGeolocation from '~/hooks/sync_geolocation';
import {checkRequest} from '~/hooks/sync_geolocation/Useful';

const HubsoftBackgroundGeolocation = ({
    navigation,
    nivel_bateria,
    modelo_dispositivo,
    fabricante,
    modificaNivelBateria,
    modificaDispositivoInfo,
}) => {
    const dispatch = useDispatch();
    const handleSync = useSyncGeolocation();

    const [enabled, setEnabled] = useState(false);

    useEffect(() => {
        // Força a captura de localizações
        if ((__DEV__ && enabled) || (enabled && Platform.OS === 'ios')) {
            BackgroundGeolocation.changePace(true)
                .then(success => console.log('changePace(true):', success))
                .catch(err => console.error('changePace(false):', err));
        }
    }, [enabled]);

    useEffect(() => {
        /// 1.  Subscribe to events.
        const onLocation = BackgroundGeolocation.onLocation(location => {
            console.log('[onLocation]');
            _modificaDeviceInfo();
        });

        const config = {
            notification: {
                title: 'Hubsoft',
                text: 'Executando',
            },
            disableMotionActivityUpdates: true,
            desiredAccuracy: Platform.select({
                ios: BackgroundGeolocation.DESIRED_ACCURACY_NAVIGATION,
                android: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
            }),
            debug: __DEV__ ? true : false,
            logLevel: __DEV__
                ? BackgroundGeolocation.LOG_LEVEL_VERBOSE
                : BackgroundGeolocation.LOG_LEVEL_OFF,
            stopOnTerminate: false,
            startOnBoot: false,
            url: ${httpClient.defaults.baseURL}api/v1/app/geolocalizacao/usuario,
            batchSync: true,
            autoSync: true,
            autoSyncThreshold: 5,
            maxBatchSize: 100,
            distanceFilter: Platform.select({ios: 10, android: 20}),
            stopTimeout: 5,
            locationTemplate:
                '{"latitude":<%= latitude %>,"longitude":<%= longitude %>,"velocidade":<%= speed %>,"time":"<%= timestamp %>","id":"<%= uuid %>","nivel_bateria": <%= battery.level %>}',
            headers: {
                accept: 'application/json',
                'content-type': 'application/json',
                authorization: httpClient.defaults.headers.common.Authorization,
            },
            params: {
                timezone: 'America/Recife',
            },
            extras: {
                modelo_dispositivo,
                fabricante,
            },
            //iOS Options
            showsBackgroundLocationIndicator: true,
            stationaryRadius: 10,
            heartbeatInterval: Platform.select({ios: 10, android: 60}),
            preventSuspend: Platform.select({ios: true, android: false}),
            activityType:
                BackgroundGeolocation.ACTIVITY_TYPE_AUTOMOTIVE_NAVIGATION,
        };

        /// 2. ready the plugin.
        BackgroundGeolocation.ready(config)
            .then(state => {
                const handler = () => {
                    setEnabled(state.enabled);

                    console.log(
                        '- BackgroundGeolocation is configured and ready: ',
                        state.enabled,
                    );

                    // Sincroniza geolocalizações
                    // ao iniciar APP
                    if (state.enabled) {
                        _handleSync();
                    }
                };

                handler();
            })
            .catch(err => {
                console.error(err);
            });

        return () => {
            // Remove BackgroundGeolocation event-subscribers when the View is removed or refreshed
            // during development live-reload.  Without this, event-listeners will accumulate with
            // each refresh during live-reload.
            onLocation.remove();
        };
    }, [
        fabricante,
        _handleSync,
        modelo_dispositivo,
        _modificaDeviceInfo,
        _verificaStatusGeolocalizacao,
    ]);

    /// 3. start / stop BackgroundGeolocation
    React.useEffect(() => {
        _verificaStatusGeolocalizacao();
    }, [_verificaStatusGeolocalizacao]);

    const _getCurrentPosition = useCallback(() => {
        BackgroundGeolocation.getCurrentPosition({
            timeout: 30, // 30 second timeout to fetch location
            maximumAge: 5000, // Accept the last-known-location if not older than 5000 ms.
            desiredAccuracy: 10, // Try to fetch a location with an accuracy of 10 meters.
            samples: 3, // How many location samples to attempt.
            extras: {
                // Custom meta-data.
                fabricante,
                modelo_dispositivo,
            },
        })
            .then(location => {
                console.log('[_getCurrentPosition]: ', location);
                handleSync();
            })
            .catch(e => {
                console.error(
                    '[_getCurrentPosition]: Falha ao consultar localização atual',
                );
                console.error(e);
            });
    }, [fabricante, modelo_dispositivo, handleSync]);

    const _handleSync = useCallback(() => {
        try {
            _getCurrentPosition();
        } catch (e) {
            console.error('[_handleSync]: Falhou');
            console.error(e);
        }
    }, [_getCurrentPosition]);

    const _verificaStatusGeolocalizacao = useCallback(async () => {
        try {
            /*
             * Verifica se já excedeu o intervalo
             * de 2 minutos desde a última verificação do
             * status de geolocalização
             */
            const allowReq = await checkRequest('ultima_verificacao', 2);

            if (!allowReq) {
                return;
            }

            const state = await NetInfo.fetch();

            if (state.isConnected) {
                httpClient
                    .get(
                        'api/v1/app/geolocalizacao/usuario/rastreamento/status',
                    )
                    .then(response => {
                        dispatch({
                            type: types.MODIFICA_ULTIMA_VERIFICACAO_GEOLOCALIZACAO,
                            payload: {ultima_verificacao: moment()},
                        });

                        if (response.data.status === 'success') {
                            _handleBgGeolocation(response.data.iniciar);
                        } else {
                            _handleBgGeolocation(true);
                        }
                    })
                    .catch(err => {
                        Unauthorized(dispatch, err);
                    });
            } else {
                _handleBgGeolocation(true);
            }
        } catch (e) {
            console.error(e);
            showToast(
                'error',
                'Problema',
                'Falha ao tentar verificar status do rastreamento',
            );
        }
    }, [dispatch, _handleBgGeolocation]);

    const _handleBgGeolocation = useCallback(
        iniciar => {
            const _dispatch = () => {
                dispatch({
                    type: types.MODIFICA_RASTREAMENTO_USUARIO,
                    payload: {
                        rastreamento_usuario: iniciar,
                    },
                });
            };

            if (!enabled && iniciar) {
                BackgroundGeolocation.start()
                    .then(state => console.log('[start] success - ', state))
                    .catch(e => console.error(e))
                    .finally(() => _dispatch());
            } else if (!iniciar && AppState.currentState === 'active') {
                BackgroundGeolocation.stop()
                    .then(() => _dispatch())
                    .catch(() => _dispatch());
            }
        },
        [dispatch, enabled],
    );

    const _modificaDeviceInfo = useCallback(() => {
        try {
            DeviceInfo.getBatteryLevel().then(batteryLevel => {
                let value = Math.round(batteryLevel.toFixed(2) * 100);
                modificaNivelBateria(value);
            });

            modificaDispositivoInfo({
                modelo_dispositivo: DeviceInfo.getModel(),
                fabricante: DeviceInfo.getBrand(),
            });
        } catch (e) {
            console.error(e);
        }
    }, [modificaDispositivoInfo, modificaNivelBateria]);

    return <Fragment />;
};

const mapStateToProps = state => ({
    nivel_bateria: state.DeviceInfoReducer.nivel_bateria,
    modelo_dispositivo: state.DeviceInfoReducer.modelo_dispositivo,
    fabricante: state.DeviceInfoReducer.fabricante,
});

const actions = {
    modificaNivelBateria,
    modificaDispositivoInfo,
    modificaStatusConexao,
};

export default connect(mapStateToProps, actions)(HubsoftBackgroundGeolocation);

macielrsf avatar Mar 20 '23 11:03 macielrsf

Ignore last message. Wrong issue.

christocracy avatar Mar 20 '23 13:03 christocracy

@christocracy good morning! Is there something I could be doing wrong? According to the stack trace, it could be something to do with the permission granted.

macielrsf avatar Mar 22 '23 11:03 macielrsf

Show me $ adb logcat *:S TSLocationManager:V of your app launching from a fresh install

christocracy avatar Mar 22 '23 14:03 christocracy

Please don't post screenshots of logs.

In terminal window, execute the following.

$ adb logcat *:S TSLocationManager:V

christocracy avatar Mar 23 '23 13:03 christocracy

Show me $ adb logcat *:S TSLocationManager:V of your app launching from a fresh install

TSLocationManagerLogs.zip

macielrsf avatar Mar 23 '23 13:03 macielrsf

Show me $ adb logcat *:S TSLocationManager:V of your app launching from a fresh install

TSLocationManagerLogs.zip

I exported the logs from Flipper in JSON format for easy viewing. Would it be this ?

macielrsf avatar Mar 23 '23 17:03 macielrsf

See wiki “Debugging” and learn to use the method .emailLog

christocracy avatar Mar 23 '23 17:03 christocracy

Hi @christocracy,

To use .emailLog, do I need to set debug as "true" on production ? For this bug, logLevel as "LOG_LEVEL_ERROR", will it be enough ?

Will sounds and notifications be trigged on production ? I wouldn't like it.

macielrsf avatar Mar 27 '23 11:03 macielrsf

emailLog has nothing to do with debug: true.

Use LOG_LEVEL_VERBOSE

christocracy avatar Mar 27 '23 11:03 christocracy

Are there any workaround that I can use it ? Like a timeout to initialize or config BackgroundGeolocation ?

I was checking android-permissions dependency of this library and I found this old issue: https://github.com/nishkarsh/android-permissions/issues/16

Is there something similar with current issue ?

macielrsf avatar Apr 04 '23 17:04 macielrsf

Is there something similar with current issue ?

No, it’s unrelated.

I suggest you install the latest version. You are the only person reporting this issue.

let me know if you know how to reproduce it.

christocracy avatar Apr 04 '23 20:04 christocracy

This issue is stale because it has been open for 30 days with no activity.

github-actions[bot] avatar May 24 '24 01:05 github-actions[bot]

This issue was closed because it has been inactive for 14 days since being marked as stale.

github-actions[bot] avatar Jun 07 '24 01:06 github-actions[bot]