Skip to content

Background location tracking - GPS distance request#78279

Merged
AndrewGable merged 18 commits intoExpensify:mainfrom
software-mansion-labs:@GCyganek/gps/background-location-tracking
Jan 19, 2026
Merged

Background location tracking - GPS distance request#78279
AndrewGable merged 18 commits intoExpensify:mainfrom
software-mansion-labs:@GCyganek/gps/background-location-tracking

Conversation

@GCyganek
Copy link
Contributor

@GCyganek GCyganek commented Dec 22, 2025

Explanation of Change

Adds long-running background task for location tracking and processing location updates.

Translations for the Android notification title and body will be updated later today as I can't run the script myself

Fixed Issues

$ #77219 and #77220 and #77225

MOBILE-EXPENSIFY: https://github.com/Expensify/Mobile-Expensify/pull/13808

Tests

Using simulators:

iOS: Simulator has Features > Location options that when triggered can simulate location change events

Screenshot 2026-01-13 at 12 48 17

Android: Tap ... on the sidebar with additional simulator controls > Location > Create a route and tap Play route

Screenshot 2026-01-13 at 12 47 56

Using physical devices:

You can either cover the distance for real or use applications that simulate location changes like Lockito

  1. FAB > Track Distance
  2. GPS tab > Tap Start (grant all required location permissions if needed)
  3. Cover/simulate some distance
  4. Tap Stop
  5. Verify that start & end addresses + total distance are correct

With backgrounding the app:

  1. FAB > Track Distance
  2. GPS tab > Tap Start (grant all required location permissions if needed)
  3. Cover/simulate some distance
  4. In the meantime background the app for some during the trip (make sure that there is a GPS tracking in progress notification on Android that opens the app when tapped)
  5. Open the app again
  6. Continue the trip or go to the next point (make sure that the trip is updated with the distance covered when the app was backgrounded)
  7. Tap Stop
  8. Verify that start & end addresses + total distance are correct
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Same as Tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Screen.Recording.2026-01-13.at.12.54.09.mov
iOS: Native
Screen.Recording.2026-01-13.at.11.58.23.mov

@melvin-bot
Copy link

melvin-bot bot commented Dec 22, 2025

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx ts-node ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx ts-node ./scripts/generateTranslations.ts --compare-ref main

@github-actions
Copy link
Contributor

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

@codecov
Copy link

codecov bot commented Dec 22, 2025

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/App.tsx 100.00% <ø> (ø)
src/utils/geodesicDistance.ts 100.00% <100.00%> (ø)
...OURequestStepDistanceGPS/DistanceCounter/index.tsx 0.00% <0.00%> (ø)
...ou/request/step/IOURequestStepDistanceGPS/const.ts 33.33% <33.33%> (ø)
...equestStepDistanceGPS/utils/coordinatesToString.ts 0.00% <0.00%> (ø)
...tep/IOURequestStepDistanceGPS/GPSButtons/index.tsx 0.00% <0.00%> (ø)
...equestStepDistanceGPS/utils/addressFromGpsPoint.ts 0.00% <0.00%> (ø)
src/libs/actions/GPSDraftDetails.ts 0.00% <0.00%> (ø)
...tup/backgroundLocationTrackingTask/index.native.ts 6.25% <6.25%> (ø)
... and 4 files with indirect coverage changes

@GCyganek GCyganek marked this pull request as ready for review January 13, 2026 12:16
@GCyganek GCyganek requested review from a team as code owners January 13, 2026 12:16
@melvin-bot melvin-bot bot requested review from JmillsExpensify and dukenv0307 and removed request for a team January 13, 2026 12:16
@melvin-bot
Copy link

melvin-bot bot commented Jan 13, 2026

@dukenv0307 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@GCyganek
Copy link
Contributor Author

@JmillsExpensify please ignore, @dukenv0307 and @AndrewGable will review this

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c97eb17783

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 95 to 97
<Button
onPress={onNext}
onPress={navigateToNextStep}
success

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reinstate zero-distance guard before Next

The Next button now directly calls navigateToNextStep without any check for distanceInMeters === 0, and showZeroDistanceModal is never set anywhere in this file. This means a user who stops a trip without moving (e.g., only one GPS point) can proceed without the zero‑distance warning that previously fired, leading to a 0‑distance request and missing feedback. Consider restoring a guard that shows the zero‑distance modal (or blocks navigation) when no distance was recorded.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Mr. Computer


const newGpsPoints = data.locations.map((location) => ({lat: location.coords.latitude, long: location.coords.longitude}));

addGpsPoints(newGpsPoints);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addGpsPoints we're getting gpsDraftDetails, which can cause a race condition while setStartAddress is updating data

Copy link
Contributor Author

@GCyganek GCyganek Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think it's not causing any problems here as in addGpsPoints we are not using startAddress, but it's true that this it could potentially result in a bug in the future, so I will pass gpsDraftDetails to addGpsPoints instead of reading it again, thanks

@dubielzyk-expensify
Copy link
Contributor

Tested this today and it looks mostly good. Two things:

  1. I believe this error shows up even if the start and stop location isn't the same? But just less than 0.0 miles? If so I recon we should tweak the copy to say something more like "can't create expense with a trip that small" or something. Can you confirm this is the case?
CleanShot 2026-01-15 at 09 56 08@2x
  1. The tooltip pops up when you've started the trip, dismissed the tooltip, then go back to inbox, then back to the trip. It should only show up when you've started the trip and not each time you come back to an in-progress trip.
image

You can see that the tooltip above shows up when it's 4.8 miles already and I've dismissed the tooltip already. Otherwise this looks great 👍

@GCyganek
Copy link
Contributor Author

  1. I believe this error shows up even if the start and stop location isn't the same? But just less than 0.0 miles? If so I recon we should tweak the copy to say something more like "can't create expense with a trip that small" or something. Can you confirm this is the case?

We only check if covered distance is equal to 0 to show this modal.

You can see here I have different start and end addresses and 0.0 distance (recorded distance between start and end is 0.03mi) and it doesnt show me the modal when I tap Next:

Screenshot 2026-01-15 at 10 10 17

2. The tooltip pops up when you've started the trip, dismissed the tooltip, then go back to inbox, then back to the trip. It should only show up when you've started the trip and not each time you come back to an in-progress trip.

Can we decide which conditions need to be met to stop showing the tooltip? Currently we show it all the time until user has successfully created GPS expense. I believe we want to change it so that when user closes it it won't be shown until the next GPS trip is started OR until user has created the GPS expense?

Two examples:

  1. User started the trip, closed the tooltip (so now it's not being shown anymore when he opens the GPS screen again), stopped the trip after covering some distance, then discarded it and taps Start again. The trip starts over and the tooltip is being shown again.
  2. User started the trip, closed the tooltip (so now it's not being shown anymore when he opens the GPS screen again), stopped the trip after covering some distance, then created an expense using trip data, comes back to GPS screen and taps Start again. The trip starts over and the tooltip is not being shown ever again since the GPS expense has been created.

Are these examples what we want to have? Or if user closes the tooltip then we don't want to show it ever again?

@dannymcclain
Copy link
Contributor

You can see here I have different start and end addresses and 0.0 distance (recorded distance between start and end is 0.03mi) and it doesnt show me the modal when I tap Next:

Do we need to increase the amount of decimal places we show in the big distance display if we can in fact create expenses with distances of 0.01–0.09? It looks really weird to be able to create expense when the thing is showing 0.0 mi.

@GCyganek
Copy link
Contributor Author

Do we need to increase the amount of decimal places we show in the big distance display if we can in fact create expenses with distances of 0.01–0.09? It looks really weird to be able to create expense when the thing is showing 0.0 mi.

I think showing two decimal places would make sense

@dubielzyk-expensify
Copy link
Contributor

I think showing two decimal places would make sense

Curious what @AndrewGable thinks here. My loose take is that maybe we leave it as is but change the error message to be "Distance travelled must be greater than 0 miles". I guess we need to figure out what we want here. Should someone be able to submit distance for 0.01 miles? Part of me suggests the other way because that's still same as olddot, just with a better error message.

Are these examples what we want to have? Or if user closes the tooltip then we don't want to show it ever again?

Hmm curious for @dannymcclain 's take here. My thinking is that we either do option 2 and never show it after you've created one GPS expense. Or we just show it at the start of each journey. Mostly what I wanted to not have is that you can see it when you come back to an in-progress trip.

@GCyganek
Copy link
Contributor Author

Curious what @AndrewGable thinks here. My loose take is that maybe we leave it as is but change the error message to be "Distance travelled must be greater than 0 miles". I guess we need to figure out what we want here. Should someone be able to submit distance for 0.01 miles? Part of me suggests the other way because that's still same as olddot, just with a better error message.

But still this message wouldn't be entirely correct, because 0.01 is still more than 0, right? Currently we just check if there is more than one GPS point recorded. In real-life scenario I don't think people would record 0.01 miles trips and blocking it and setting some boundary value may be problematic, because if we set it to 0.1 mi, then why not 0.5 mi? If 1 mi, then why wouldn't we allow 0.9 mi and so on.

As we have location updates set to happen around every 0.06 mi (100 meters), I think it wouldn't be a bad idea to just start showing 2 decimal places instead of 1 and leave the rest as is, but it's just my take on this issue

Hmm curious for @dannymcclain 's take here. My thinking is that we either do option 2 and never show it after you've created one GPS expense. Or we just show it at the start of each journey. Mostly what I wanted to not have is that you can see it when you come back to an in-progress trip.

I think that when the user closes the tooltip it's because they don't want it to ever be shown again in the same scenario, so maybe we only show it at the start of each trip (not again when user comes back to ongoing GPS trip from other screen) until user has closed the tooltip or has created a GPS expense?

@dannymcclain
Copy link
Contributor

maybe we only show it at the start of each trip (not again when user comes back to ongoing GPS trip from other screen) until user has closed the tooltip or has created a GPS expense?

This sounds good to me. I think the user explicitly closing it or completing a GPS expense are both clear signals that we don't need to show it again.

As far as the decimal places go, I'm really keen to hear what @AndrewGable (and @JmillsExpensify @trjExpensify) think about it.

@trjExpensify
Copy link
Contributor

I don't have a super strong opinion. I think two decimals for distance feels a bit technical, but this rationale does make sense if that's the nature of how we record:

As we have location updates set to happen around every 0.06 mi (100 meters), I think it wouldn't be a bad idea to just start showing 2 decimal places instead of 1 and leave the rest as is, but it's just my take on this issue

@dubielzyk-expensify
Copy link
Contributor

This sounds good to me. I think the user explicitly closing it or completing a GPS expense are both clear signals that we don't need to show it again.

Okay, let's go with that then 👍 Show once, not again.

I don't have a super strong opinion. I think two decimals for distance feels a bit technical, but this rationale does make sense if that's the nature of how we record:

I agree with Tom on the technical. Feels so weird to me when driving in a car. How about we just leave it as is then unless someone else feels strongly. Mostly what I didn't like initially was that it said "Start and stop can't be the same" but in my instance the address was different. Maybe it's just about tweaking the language slightly, but also it's probably not a big deal.

@GCyganek
Copy link
Contributor Author

Mostly what I didn't like initially was that it said "Start and stop can't be the same" but in my instance the address was different.

Yeah the problem probably was that it recorded just two points and the distance between them was <0.05mi (although I set location updates to happen every 0.06mi which should round off to 0.1 on the distance counter, they may vary slightly in the distance between them), so the start & stop addresses were different and it was showing 0.0mi

@dubielzyk-expensify so for now I will post a follow-up PR soon with the change to the tooltip to not show it ever again if user closed it (even for new GPS trips).

I like the discussion and we can continue it here if needed (regarding the number of decimals for the distance counter & tooltips), but I don't think it's blocking this PR and we can merge it to be able to finally put other PRs to review and get closer to the first release of this feature. cc: @AndrewGable

@dannymcclain
Copy link
Contributor

Sounds good to me 👍

@AndrewGable AndrewGable merged commit a7d108a into Expensify:main Jan 19, 2026
38 checks passed
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/AndrewGable in version: 9.3.5-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@IuliiaHerets
Copy link

@GCyganek GPS is not being counted even though it’s enabled and all permissions are granted. We even tried walking in real life

ScreenRecording_01-21-2026.13-36-27_1.MP4

Is this an issue, or do we need to do some additional settings?

@GCyganek
Copy link
Contributor Author

GCyganek commented Jan 21, 2026

@GCyganek GPS is not being counted even though it’s enabled and all permissions are granted. We even tried walking in real life

location updates should be triggered every 100 meters (=0.06mi) covered, which means even if you have a trip enabled for even 1 hour but not walked at least 100 meters from the starting point, there will be no distance update. Have you walked at least 100 meters?

Could you also send a screenshot of location permissions set for the expensify app? Just to make sure it was started with precise location on and with Allow all the time location permission set

@IuliiaHerets
Copy link

@GCyganek tester tried to walk longer in an open place, it triggers the counting, but he can not tap on continue. 0.1 mil is too short?

az_recorder_20260121_194628.mp4
PXL_20260121_123822336.TS.mp4

@IuliiaHerets
Copy link

@GCyganek It would be better to test this internally, as we’re having issues validating this PR

@GCyganek
Copy link
Contributor Author

he can not tap on continue. 0.1 mil is too short?

Next button handling is not implemented yet. This PR only adds implementation of GPS location tracking, so as long as the location is tracked correctly everything is ok.

@GCyganek
Copy link
Contributor Author

Looking at the videos, it looks like location tracking is working, 0.1mi to appear takes longer than when you have km set as distance unit as 0.1mi is ~160meters so you have to walk longer to see change

@IuliiaHerets
Copy link

@GCyganek passed the PR, thanks for your help

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/Beamanator in version: 9.3.5-7 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants