4

I am having an issue within react native where I am downloading a base 64 string from an API and rendering it as a PDF on mobile devices.

The project is built with React native - the code works fine on iOS but on Android we are getting a 'bad base 64' / invalid PDF format error.

Code:

//fetch on button click
 getBill = () => {
    if (Platform.OS === "android") {
      this.getAndroidPermission();
    } else {
      this.downloadBill();
    }
  };

//check user has permissions on device (only for android)
getAndroidPermission = async () => {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
      );
      const grantedRead = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE
      );

      if (
        granted === PermissionsAndroid.RESULTS.GRANTED &&
        grantedRead === PermissionsAndroid.RESULTS.GRANTED
      ) {
        this.downloadBill();
      } else {
        Alert.alert(
          "Permission Denied!",
          "You need to give storage permission to download the file"
        );
      }
    } catch (err) {
      console.warn(err);
    }
  };

//download and display bill
downloadBill = async () => {
    this.setState({ loading: true });
    let billId = this.state.billId;
    let user = await AsyncStorage.getItem("user");
    let parseUser = JSON.parse(user);
    let userToken = parseUser.token;

    RNFetchBlob.config({
      addAndroidDownloads: {
        useDownloadManager: true,
        notification: true,
        path:
          RNFetchBlob.fs.dirs.DownloadDir +
          "/" +
          `billID_${this.state.billId}.pdf`,
        mime: "application/pdf",
        description: "File downloaded by download manager.",
        appendExt: "pdf",
        trusty: true,
      },
    })
      .fetch("GET", `${config.apiUrl}/crm/getbillfile/${billId}`, {
        Authorization: "Bearer " + userToken,
      })
      .then((resp) => {
        let pdfLocation =
          RNFetchBlob.fs.dirs.DocumentDir +
          "/" +
          `billID_${this.state.billId}.pdf`;

        RNFetchBlob.fs.writeFile(pdfLocation, resp.data, "base64");

        FileViewer.open(pdfLocation, {
          onDismiss: () => this.setState({ loading: false }),
        });
      });
  }; 

Android manifest:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> 
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
  </intent-filter>

Any help would be appreciated

Gavin Crawley
  • 335
  • 3
  • 14
  • 25
  • Have you tried to define a different MIME type? – CuriousSuperhero Sep 08 '20 at 10:38
  • You probably should add a `.catch()` after the `.then()` as well. I can imagine that the response already fails silently, maybe because of access restrictions. Then resp.data would not contain what you expect (and obviously can not be parsed as base64). – kca Sep 08 '20 at 12:49
  • @CuriousSuperhero yes we have tried octet-stream as well but this has failed. – Gavin Crawley Sep 08 '20 at 14:14
  • @kicia - this is returning valid base64 when running on iOS- we are able to log the output and convert it fine using https://base64.guru/converter/decode/pdf – Gavin Crawley Sep 08 '20 at 14:15
  • @kicia added this as you noted and its creating the file ok without exceptions - its only when trying to open the PDF file that it fails then with the error described – Gavin Crawley Sep 08 '20 at 14:33

1 Answers1

0

You are giving the wrong file path to open in android, whereas in IOS, it is actually saving in the correct place and opening from the correct place.

Check the OS before opening the file and then open the file.

....
const fpath = `${RNFetchBlob.fs.dirs.DownloadDir}${filename}`;
RNFetchBlob.config({
        addAndroidDownloads: {
            useDownloadManager: true,
            notification: true,
            path: fpath,
            description: "File downloaded by download manager.",
        },
    })
    .fetch("GET", `${config.apiUrl}/crm/getbillfile/${billId}`, {
        Authorization: "Bearer " + userToken,
    })
    .then((resp) => {
        if (OS == "ios") {
            // ... do existing logic    
        } else if (OS === "android") {
            // FileViewer or RNFetchBlob should work.
            RnFetchBlob.android.actionViewIntent(fpath, "application/pdf");
        }
    });
};
senthil balaji
  • 588
  • 4
  • 18
  • Hi Senthil unfortunately the issue remains the same - we have tried opening the file from the path directly after it failed but its giving an invalid PDF format message. We opened this through notepad and converted using base64.guru site and everything rendered fine. Still no closer to a solution unfortunately – Gavin Crawley Sep 14 '20 at 11:12
  • @GavinCrawley is it possible, you send that PDF link somewhere non-secured, anyone can check in their local machine as well. – senthil balaji Sep 15 '20 at 02:15
  • Hi Senthil, no location seems to be ok. We have went in through the fileviewer on android and can see its there. I dont think theres any issue with location or where its downloading from. I think the issue is to do with how the octect-stream is being decoded on android – Gavin Crawley Sep 16 '20 at 10:50