Downloading and Saving binary files using React Native with Expo

image

A key part of the app I'm building (JiffyCV) is to generate a PDF of the document you've been creating, but when it came to implementing the saving of the PDF there wasn't much documentation so I'm going write some in the hope this helps others looking to add this functionality to their app.

In order to download and save the PDF in my app I've had to implement the following flow:

  • Make the request to the server with axios.

  • Turn the response's data in to a base64 string using Buffer.

  • Get a path for the file to be saved under.

  • Write the file to the path in order to have it available to share.

  • Initiate the device's sharing functionality to share the file.

  • The user then has the option to save the file to the file system or share it elsewhere.

Libraries used

There are a number of libraries that are needed to implement the flow described above, most are universally needed but axios is more of a personal preference.

The libraries are:

  • axios — For making the requests to server

  • jest-mock-axios — For mocking out the server response during testing

  • buffer — For turning the response from server into base64 string

  • expo-file-system — For saving the file to app's doc store before we share it

  • expo-sharing — For allowing the user to share/save the file to device

Getting the file from the server

My app is using a serverless function to create a PDF using puppeteer and then returning the PDF object.

The response from the serverless function is base64 encoded but I'm using the arraybuffer responseType of axios which means I need to use buffer to convert these into a base64 string I can use to create the file locally.

This was the only approach with axios that I found worked.

Saving the file to the app's document store

Once we have the base64 string for the PDF we want to save we need to create the path we're going to save the file under and save it to the app's document directory.

You can get the path to save the file under by first getting the app's document directory using the documentDirectory property from expo-file-system. You then need to append the filename you'll save the file under.

The document directory will have the / suffixed to it so there's no need include this in your file name and you'll need to make sure your filename is URI encoded as if it's not you may end up with half a filename being shown to the user.

After getting the path to save the file under you can call writeAsStringAsync from expo-file-system passing it the filename, the base64 representation of the file, and as you're using base64 you'll need to pass it an options object with the encoding property set to EncodingType.Base64.

Allowing the user to share/save the file

This part is relatively simple as you just need to pass the path to the saved file in the app's document store to the shareAsync function of expo-sharing. This will then bring up the dialog to allow the user to save the file to their device or share it via the apps they have installed on their phone.

Testing the flow

In order to test the calls to the server with axios you can use the jest-mock-axios library which allows for asserting that an endpoint was called as well as allowing for response values to be asserted against.

The jest-mock-axios way of stubbing server responses isn't the most elegant solution as it requires you to define the stubbed response after calling the axios code, but once you have the hang of the flow it works well.

When using jest-mock-axios it's very important to remember to call mockAxios.reset() after each test, as if you don't the response stubbing won't work as expected.

Once the call to the server and its response are mocked then you'll need to mock the expo-file-system and expo-sharing modules in order to control how the documentDirectory, writeAsStringAsync, and shareAsync calls behave.

You can use the standard jest.mock approach for this as shown below. As long as the module mocks return an object with properties for the functions you are calling then it'll work. If you want to control the way each function behaves or assert the function was called, then using a named mock and use mockImplementation can change the behaviour.

Summary

Using a combination of expo-file-system and expo-sharing allows for an easy to implement test solution and the ability for the user to decide what they want to do with the file after is a really nice user experience, as they may not always wish to save it but instead send it via another app.

That's it for this topic. Thank you for reading.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics