pawan-pk / react-native-mapbox-navigation

Mapbox React Native SDKs enable interactive maps and real-time, traffic-aware turn-by-turn navigation, dynamically adjusting routes to avoid congestion.
MIT License
11 stars 4 forks source link

Expo managed workflow #12

Closed syedmumersajjad closed 2 weeks ago

syedmumersajjad commented 3 weeks ago

Hi thanks for sharing this library can we use this library in managed workflow.

jacquesngomeheffa commented 3 weeks ago

@syedmumersajjad Yes it's possible. I'm currently working with this package. I had to write a plugin to let it works with EXPO.

But it's possible

syedmumersajjad commented 3 weeks ago

Thanks for your reply how long it will take to publicly available because I need it badly

Nocolais95 commented 3 weeks ago

Yes, I had to create this plugin:

` / eslint-disable no-undef / const { withDangerousMod, withPlugins, } = require('@expo/config-plugins'); const { writeFileSync, existsSync, mkdirSync } = require('fs');

function addLayoutFiles(resDirectory, filename, file) { const layoutDir = ${resDirectory}; const layoutDirExists = existsSync(layoutDir); if (layoutDirExists) { writeFileSync(${layoutDir}/${filename}, file); } else { mkdirSync(layoutDir); writeFileSync(${layoutDir}/${filename}, file); } }

function withAndroidBridgeFiles(config) { return withDangerousMod(config, [ 'android', (cfg) => { const androidProjRoot = cfg.modRequest.platformProjectRoot; const navViewActivityLayoutFileName = 'mapbox_access_token.xml';

  const layoutFile = `<?xml version="1.0" encoding="utf-8"?>
YOUR_MAPBOX_ACCESS_TOKEN `; addLayoutFiles( `${androidProjRoot}/app/src/main/res/values`, navViewActivityLayoutFileName, layoutFile ); return cfg; }, ]); } function withMapboxNavigation(config) { return withPlugins(config, [ withAndroidBridgeFiles, ]); } module.exports = withMapboxNavigation; ` And then, you must add this plugin in your app.json: `"plugins": [ [ "./plugin/mapboxNavigation/index", ], ]` Due to time constraints, I had to implement another library ("@rnmapbox/maps") to use the MAPBOX_DOWNLOAD_TOKEN in the AndroidManifest, but I believe this library could be avoided if the plugin is properly created and managed: `"plugins": [ [ "./plugin/mapboxNavigation/index", ], [ "@rnmapbox/maps", { "RNMapboxMapsImpl": "mapbox", "RNMapboxMapsDownloadToken": "YOUR_MAPBOX_DOWNLOAD_TOKEN" } ], ]`
jacquesngomeheffa commented 3 weeks ago

@syedmumersajjad Here my plugin

const {
  withGradleProperties,
  withAndroidManifest,
  withInfoPlist,
  withDangerousMod
} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');

const withCustomWorkLibrary = (config) => {
  config = withGradleProperties(config, async (config) => {
    setMapboxDownloadsToken(config);
    return config;
  });

    config = withAndroidManifest(config, async (config) => {
      addMapboxAccessToken(config);
      //addForegroundServiceLocationPermission(config);
      return config;
    });
  //}

  // iOS configurations
  config = withInfoPlist(config, async (config) => {
    addMapboxAccessTokenToInfoPlist(config);
    addUIBackgroundModes(config);
    return config;
  });

  config = withDangerousMod(config, [
    'ios',
    (config) => {
      addNetrcFile();
      modifyPodfile(config.modRequest.projectRoot);
      return config;
    },
  ]);

  return config;
};

function setMapboxDownloadsToken(config) {
  const { modResults } = config;
  const mapboxToken = MAPBOX_DOWNLOADS_TOKEN;
  const tokenKey = 'MAPBOX_DOWNLOADS_TOKEN';

  if (Array.isArray(modResults)) {
    const tokenIndex = modResults.findIndex((item) => item.key === tokenKey);
    if (tokenIndex !== -1) {
      // Replace existing token
      modResults[tokenIndex].value = mapboxToken;
    } else {
      // Add new token
      modResults.push({
        type: 'property',
        key: tokenKey,
        value: mapboxToken,
      });
    }
  } else {
    throw new Error('gradleProperties is not an array');
  }
}

function addMapboxAccessTokenToInfoPlist(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  modResults['MBXAccessToken'] = mapboxAccessToken;
}

function addUIBackgroundModes(config) {
  const { modResults } = config;

  if (!modResults.UIBackgroundModes) {
    modResults.UIBackgroundModes = [];
  }

  if (!modResults.UIBackgroundModes.includes('audio')) {
    modResults.UIBackgroundModes.push('audio');
  }

  if (!modResults.UIBackgroundModes.includes('location')) {
    modResults.UIBackgroundModes.push('location');
  }
}

function addNetrcFile() {
  const homeDir = require('os').homedir();
  const netrcPath = path.join(homeDir, '.netrc');
  const secretToken = MAPBOX_DOWNLOADS_TOKEN;
  const netrcContent = `machine api.mapbox.com\nlogin mapbox\npassword ${secretToken}\n`;

  fs.writeFileSync(netrcPath, netrcContent, { mode: 0o600 });
}

function modifyPodfile(projectRoot) {
  const podfilePath = path.join(projectRoot, 'ios', 'Podfile');

  if (fs.existsSync(podfilePath)) {
    let podfileContent = fs.readFileSync(podfilePath, 'utf8');

    // Ensure the correct version of MapboxMaps is specified
    const mapboxVersion = '10.18.2';
    const regex = /pod 'MapboxMaps', '.*'/;

    if (regex.test(podfileContent)) {
      podfileContent = podfileContent.replace(regex, `pod 'MapboxMaps', '~> ${mapboxVersion}'`);
    } else {
      // Add the MapboxMaps dependency if not present
      podfileContent += `\npod 'MapboxMaps', '~> ${mapboxVersion}'\n`;
    }

    fs.writeFileSync(podfilePath, podfileContent, 'utf8');
  } else {
    throw new Error('Podfile not found');
  }
}

function addMapboxAccessToken(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  const application = modResults.manifest.application[0];

  if (!application.hasOwnProperty('meta-data')) {
    application['meta-data'] = [];
  }

  const metaDataItem = application['meta-data'].find((item) => item['$']['android:name'] === 'MAPBOX_ACCESS_TOKEN');

  if (metaDataItem) {
    // If the meta-data item exists, update its value and add the attributes
    metaDataItem['$']['android:value'] = mapboxAccessToken;
    metaDataItem['$']['translatable'] = 'false';
    metaDataItem['$']['tools:ignore'] = 'UnusedResources';
  } else {
    // If the meta-data item does not exist, add it with the attributes
    application['meta-data'].push({
      $: {
        'android:name': 'MAPBOX_ACCESS_TOKEN',
        'android:value': mapboxAccessToken,
        'translatable': 'false',
        'tools:ignore': 'UnusedResources'
      }
    });
  }
}

module.exports = withCustomWorkLibrary;

Inside the App.json => pluggin add ["./plugins/withCustomWorkLibrary.js",{}],

Nocolais95 commented 3 weeks ago

jacquesngomeheffa

I can see it's better than my plugin, in fact you added to iOS. Congratulations my friend, I'm going to use.

We should add this plugin to the library

syedmumersajjad commented 3 weeks ago

@jacquesngomeheffa thanks for sharing but still getting errors can you share your project it will helps a lot for newbie like me

syedmumersajjad commented 3 weeks ago

@jacquesngomeheffa please i need it badly nothing working for me can you give me in separate project

jacquesngomeheffa commented 3 weeks ago

@syedmumersajjad Share your error, I will take a look at it?

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa this error occurred when simulator opens Translated Report (Full Report Below)

Incident Identifier: BAD0009B-86D5-4761-898E-42942AA1EF8D CrashReporter Key: 91FE9550-7974-8D4E-BC06-971601F52573 Hardware Model: MacBookPro16,2

Coalition: com.apple.CoreSimulator.SimDevice.B8C8F3B3-9BAA-4799-9EDD-A2FD5439F7AC [24256] Responsible Process: SimulatorTrampoline [51232]

Date/Time: 2024-08-25 14:46:31.2490 +0500 Launch Time: 2024-08-25 14:46:31.1987 +0500 OS Version: macOS 14.6.1 (23G93) Release Type: User Report Version: 104

Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Termination Reason: DYLD 1 Library missing Library not loaded: @rpath/MapboxCommon.framework/MapboxCommon Referenced from: <6645BACD-586B-3B2C-A884-09B701040564> /Users/USER/Library/Developer/CoreSimulator/Devices/B8C8F3B3-9BAA-4799-9EDD-A2FD5439F7AC/data/Containers/Bundle/Application/8AFF4689-0E88-4190-85AE-614DC219379F/YurDrivers.app/YurDrivers Reason: tried: '/Library/Developer/CoreSimulator/Volumes/iOS_21F79/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.5.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift/MapboxCommon.framework/MapboxCommon' (no such file), '/usr/lib/swift/MapboxCommon.framework/MapboxCommon' (no such file, not in dyld cache), '/Users/mac/Library/Developer/CoreSimulator/Devices/B8C8F3B3-9BAA-4799-9EDD-A2FD5439F7AC/data/Containers/Bundle/Application/8AFF4689-0E88-4190-85AE-614DC219379F/YurDrivers.app/Frameworks/MapboxCommon.framework/MapboxCommon' (no such file), '/Users/mac/Library/Developer/CoreSimulator/Devices/B8C8F3B3-9BAA-4799-9EDD-A2FD5439F7AC/data/Containers/Bundle/Application/8AFF4689-0E88-4190-85AE-614 (terminated at launch; ignore backtrace)

jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad I think you have this problem because you're not using this package as a static Frameworks

Make sure you have passed a Mapbox key who has download rights, otherwise it won't work

Try the same below (My whole configuration)

Here is how my app.json looks like image

Where my plugin is located at image

How my plugin looks like

const {
  withGradleProperties,
  withAndroidManifest,
  withInfoPlist,
  withDangerousMod
} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');
const { MAPBOX_ACCESS_TOKEN, MAPBOX_DOWNLOADS_TOKEN } = require('../constants/constants');

const withCustomWorkLibrary = (config) => {
  // Android configurations

  config = withGradleProperties(config, async (config) => {
    setMapboxDownloadsToken(config);
    return config;
  });

    config = withAndroidManifest(config, async (config) => {
      addMapboxAccessToken(config);
      //addForegroundServiceLocationPermission(config);
      return config;
    });
  //}

  // iOS configurations
  config = withInfoPlist(config, async (config) => {
    addMapboxAccessTokenToInfoPlist(config);
    addUIBackgroundModes(config);
    return config;
  });

  config = withDangerousMod(config, [
    'ios',
    (config) => {
      addNetrcFile();
      modifyPodfile(config.modRequest.projectRoot);
      return config;
    },
  ]);

  return config;
};

function setMapboxDownloadsToken(config) {
  const { modResults } = config;
  const mapboxToken = MAPBOX_DOWNLOADS_TOKEN;
  const tokenKey = 'MAPBOX_DOWNLOADS_TOKEN';

  if (Array.isArray(modResults)) {
    const tokenIndex = modResults.findIndex((item) => item.key === tokenKey);
    if (tokenIndex !== -1) {
      // Replace existing token
      modResults[tokenIndex].value = mapboxToken;
    } else {
      // Add new token
      modResults.push({
        type: 'property',
        key: tokenKey,
        value: mapboxToken,
      });
    }
  } else {
    throw new Error('gradleProperties is not an array');
  }
}

function addMapboxAccessTokenToInfoPlist(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  modResults['MBXAccessToken'] = mapboxAccessToken;
}

function addUIBackgroundModes(config) {
  const { modResults } = config;

  if (!modResults.UIBackgroundModes) {
    modResults.UIBackgroundModes = [];
  }

  if (!modResults.UIBackgroundModes.includes('audio')) {
    modResults.UIBackgroundModes.push('audio');
  }

  if (!modResults.UIBackgroundModes.includes('location')) {
    modResults.UIBackgroundModes.push('location');
  }
}

function addNetrcFile() {
  const homeDir = require('os').homedir();
  const netrcPath = path.join(homeDir, '.netrc');
  const secretToken = MAPBOX_DOWNLOADS_TOKEN;
  const netrcContent = `machine api.mapbox.com\nlogin mapbox\npassword ${secretToken}\n`;

  fs.writeFileSync(netrcPath, netrcContent, { mode: 0o600 });
}

function modifyPodfile(projectRoot) {
  const podfilePath = path.join(projectRoot, 'ios', 'Podfile');

  if (fs.existsSync(podfilePath)) {
    let podfileContent = fs.readFileSync(podfilePath, 'utf8');

    // Ensure the correct version of MapboxMaps is specified
    const mapboxVersion = '10.18.2';
    const regex = /pod 'MapboxMaps', '.*'/;

    if (regex.test(podfileContent)) {
      podfileContent = podfileContent.replace(regex, `pod 'MapboxMaps', '~> ${mapboxVersion}'`);
    } else {
      // Add the MapboxMaps dependency if not present
      podfileContent += `\npod 'MapboxMaps', '~> ${mapboxVersion}'\n`;
    }

    // Ensure that other Mapbox-related pods are compatible
    const otherPods = [
      {
        name: 'MapboxNavigation',
        version: '2.18.3' // This version should be compatible with MapboxMaps 10.18.2
      }
    ];

    otherPods.forEach(pod => {
      const podRegex = new RegExp(`pod '${pod.name}', '.*'`);
      if (podRegex.test(podfileContent)) {
        podfileContent = podfileContent.replace(podRegex, `pod '${pod.name}', '~> ${pod.version}'`);
      } else {
        podfileContent += `\npod '${pod.name}', '~> ${pod.version}'\n`;
      }
    });

    fs.writeFileSync(podfilePath, podfileContent, 'utf8');
  } else {
    throw new Error('Podfile not found');
  }
}

function addMapboxAccessToken(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  const application = modResults.manifest.application[0];

  if (!application.hasOwnProperty('meta-data')) {
    application['meta-data'] = [];
  }

  const metaDataItem = application['meta-data'].find((item) => item['$']['android:name'] === 'MAPBOX_ACCESS_TOKEN');

  if (metaDataItem) {
    // If the meta-data item exists, update its value and add the attributes
    metaDataItem['$']['android:value'] = mapboxAccessToken;
    metaDataItem['$']['translatable'] = 'false';
    metaDataItem['$']['tools:ignore'] = 'UnusedResources';
  } else {
    // If the meta-data item does not exist, add it with the attributes
    application['meta-data'].push({
      $: {
        'android:name': 'MAPBOX_ACCESS_TOKEN',
        'android:value': mapboxAccessToken,
        'translatable': 'false',
        'tools:ignore': 'UnusedResources'
      }
    });
  }
}

module.exports = withCustomWorkLibrary;

This is the way I'm using this package. Additional configuration is not necessary.

My last expo build image

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa thanks for your response after setting up the project according to instructions provided above the error has gone but one more error i faced on development build to expo error: Multiple commands produce '/Users/expo/Library/Developer/Xcode/DerivedData/YurDrivers-dlhpehndovsedtakeolqvsvkwbkp/Build/Intermediates.noindex/ArchiveIntermediates/YurDrivers/InstallationBuildProductsLocation/Applications/YurDrivers.app/PrivacyInfo.xcprivacy'

duplicate output file '/Users/expo/Library/Developer/Xcode/DerivedData/YurDrivers-dlhpehndovsedtakeolqvsvkwbkp/Build/Intermediates.noindex/ArchiveIntermediates/YurDrivers/InstallationBuildProductsLocation/Applications/YurDrivers.app/PrivacyInfo.xcprivacy' on task: PhaseScriptExecution [CP] Copy Pods Resources /Users/expo/Library/Developer/Xcode/DerivedData/YurDrivers-dlhpehndovsedtakeolqvsvkwbkp/Build/Intermediates.noindex/ArchiveIntermediates/YurDrivers/IntermediateBuildFilesPath/YurDrivers.build/Debug-iphoneos/YurDrivers.build/Script-7CCF5F63BEAB1AFF30F0C986.sh (in target 'YurDrivers' from project 'YurDrivers') 
Screenshot 2024-08-26 at 10 02 24 PM
jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad did you tried this https://docs.expo.dev/build-reference/simulators/ I guess you're using a simulator right?

Better use Expo Enviroment to build the development.

If you're not using the @rnmapbox/maps doesn't install it, otherwise you'll probably facing errors

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa Yes I have used it and yes using a simulator

jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad If you're not using the @rnmapbox/maps doesn't install it, otherwise you'll probably facing errors

jacquesngomeheffa commented 2 weeks ago

any errors logs from expo environment?

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa can you share settings for android I mean which gradle you are using for it

syedmumersajjad commented 2 weeks ago

any errors logs from expo environment?

eas build --profile development --platform ios i am using development build IMG_2793

jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad I'm using Expo SDK 50, which might be why. However, I've also tested this package, and it works too. It might be helpful to you. Check it out: https://github.com/YoussefHenna/expo-mapbox-navigation.

This package is for Expo and works on SDK 50 and 51. I've tested it, and it works well.

I hope this helps you guys. I've been in your place before.

Let me know if you'd like any further changes!

jacquesngomeheffa commented 2 weeks ago

any errors logs from expo environment?

eas build --profile development --platform ios i am using development build IMG_2793

Try this one, I have changed something (Mapbox version)

const {
  withGradleProperties,
  withAndroidManifest,
  withInfoPlist,
  withDangerousMod
} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');
const { MAPBOX_ACCESS_TOKEN, MAPBOX_DOWNLOADS_TOKEN } = require('../constants/constants');

const withCustomWorkLibrary = (config) => {
  // Android configurations

  config = withGradleProperties(config, async (config) => {
    setMapboxDownloadsToken(config);
    return config;
  });

    config = withAndroidManifest(config, async (config) => {
      addMapboxAccessToken(config);
      //addForegroundServiceLocationPermission(config);
      return config;
    });
  //}

  // iOS configurations
  config = withInfoPlist(config, async (config) => {
    addMapboxAccessTokenToInfoPlist(config);
    addUIBackgroundModes(config);
    return config;
  });

  config = withDangerousMod(config, [
    'ios',
    (config) => {
      addNetrcFile();
      return config;
    },
  ]);

  return config;
};

function setMapboxDownloadsToken(config) {
  const { modResults } = config;
  const mapboxToken = MAPBOX_DOWNLOADS_TOKEN;
  const tokenKey = 'MAPBOX_DOWNLOADS_TOKEN';

  if (Array.isArray(modResults)) {
    const tokenIndex = modResults.findIndex((item) => item.key === tokenKey);
    if (tokenIndex !== -1) {
      // Replace existing token
      modResults[tokenIndex].value = mapboxToken;
    } else {
      // Add new token
      modResults.push({
        type: 'property',
        key: tokenKey,
        value: mapboxToken,
      });
    }
  } else {
    throw new Error('gradleProperties is not an array');
  }
}

function addMapboxAccessTokenToInfoPlist(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  modResults['MBXAccessToken'] = mapboxAccessToken;
}

function addUIBackgroundModes(config) {
  const { modResults } = config;

  if (!modResults.UIBackgroundModes) {
    modResults.UIBackgroundModes = [];
  }

  if (!modResults.UIBackgroundModes.includes('audio')) {
    modResults.UIBackgroundModes.push('audio');
  }

  if (!modResults.UIBackgroundModes.includes('location')) {
    modResults.UIBackgroundModes.push('location');
  }
}

function addNetrcFile() {
  const homeDir = require('os').homedir();
  const netrcPath = path.join(homeDir, '.netrc');
  const secretToken = MAPBOX_DOWNLOADS_TOKEN;
  const netrcContent = `machine api.mapbox.com\nlogin mapbox\npassword ${secretToken}\n`;

  fs.writeFileSync(netrcPath, netrcContent, { mode: 0o600 });
}

function addMapboxAccessToken(config) {
  const { modResults } = config;
  const mapboxAccessToken = MAPBOX_ACCESS_TOKEN;

  const application = modResults.manifest.application[0];

  if (!application.hasOwnProperty('meta-data')) {
    application['meta-data'] = [];
  }

  const metaDataItem = application['meta-data'].find((item) => item['$']['android:name'] === 'MAPBOX_ACCESS_TOKEN');

  if (metaDataItem) {
    // If the meta-data item exists, update its value and add the attributes
    metaDataItem['$']['android:value'] = mapboxAccessToken;
    metaDataItem['$']['translatable'] = 'false';
    metaDataItem['$']['tools:ignore'] = 'UnusedResources';
  } else {
    // If the meta-data item does not exist, add it with the attributes
    application['meta-data'].push({
      $: {
        'android:name': 'MAPBOX_ACCESS_TOKEN',
        'android:value': mapboxAccessToken,
        'translatable': 'false',
        'tools:ignore': 'UnusedResources'
      }
    });
  }
}

module.exports = withCustomWorkLibrary;

Hope this one helps.

And If you're using @rnmapbox/maps use version RNMapboxMapsVersion: 10.17.0 (IOS ONLY) and for Android use Version 11.4.0

Doesn't work try this one easy to use: https://github.com/YoussefHenna/expo-mapbox-navigation.

jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad Good luck my friend

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa thank you so much i really appreciate your work hope we will have library for managed workflow soon. Appreciated 👍

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa one last question if I use this library https://github.com/YoussefHenna/expo-mapbox-navigation do we need to add config module for that or just need to install and use it with expo prebuilt

jacquesngomeheffa commented 2 weeks ago

@syedmumersajjad Not necessary, just follow his instructions, it will be enough 👍 SmartSelect_20240827_073819_Chrome

syedmumersajjad commented 2 weeks ago

@jacquesngomeheffa okay thanks

pawan-pk commented 2 weeks ago

Thank you @jacquesngomeheffa for your resolution regarding the Expo managed project. I'm closing this issue on behalf of Comment-2311160292.