Printing dialog in my app JiffyCV
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.
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.
Making the request to the server and returning a base64 string of the response's data
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
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
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.
Saving the file to the app's document directory and then using that fileUri to allow them to share it
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.
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.
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.
Testing the calls to the server made via axios
Once the call to the server and its response are mocked then you'll need to mock the
expo-sharing modules in order to control how the
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.
By mocking out the expo-file-system and expo-sharing modules we can make sure they are called after the PDF file is generated.
Using a combination of
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.