I am trying to develop a pdf viewing ability for android, but I keep running into this error:
"FileOpener" plugin is not implemented on android
I have tried both capawesome and capacitor-community fileopener, but keep running into the same issue. I have searched through web, I have ensured it is not one of the common causes:
- Only have only one of the plugins installed to avoid issues like this:
(base) arpit@arpit-Precision-5540:~/Desktop/AI_Experiments/tlbot_website/frontend$ npm list| grep file
βββ @capacitor-community/file-opener@7.0.1
βββ @capacitor/filesystem@7.0.0
(base) arpit@arpit-Precision-5540:~/Desktop/AI_Experiments/tlbot_website/frontend$
- For viewing on the mobile, ensure you are downloading the file first.
In my case, I am able to see the file downloaded insidefiles
folder. I can open it and see the file, but my app crashes and I cannot see the file inside my app. Console logs.
[info][console] Opening PDF with FileOpener
[verbose][capacitor] callbackId: 24677522, pluginId: Filesystem, methodName: requestPermissions
[verbose][capacitor] callback: 24677522, pluginId: Filesystem, methodName: requestPermissions, methodData: {}
[verbose][capacitor] callbackId: 24677523, pluginId: Filesystem, methodName: writeFile
[verbose][capacitor] callback: 24677523, pluginId: Filesystem, methodName: writeFile, methodData: {"path":"MyPDFs\/my-pdf.pdf","data":"JVBERi0xLjQKJfbk\/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgovU3RydWN0VHJlZVJvb3QgMyAwIFIKL01hcmtJbmZvIDQgMCBSCi9MYW5nIChlbikKL1ZpZXdlclByZWZlcmVuY2VzIDUgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9UaXRsZSAoQ29weSBvZiBGb29kIFFSIENvZGUgVmVydGljYWwgQnVzaW5lc3MgQ2FyZCBCbGFjayBXaGl0ZSBNb2Rlcm4gTWluaW1hbGlzdCkKL0NyZWF0b3IgKENhbnZhKQovUHJvZHVjZXIgKENhbnZhKQovQ3JlYXRpb25EYXRlIChEOjIwMjQwOTIwMjI1ODU0KzAwJzAwJykKL01vZERhdGUgKEQ6MjAyNDA5MjAyMjU4NTQrMDAnMDAnKQovS2V5d29yZHMgKERBR1JWbkZMRUZrLEJBR0EybWQ0YXFRKQovQXV0aG9yIChhcnBpdCBwYXRlbCkKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFs3IDAgUiA4IDAgUl0KL0NvdW50IDIKPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdFRyZWVSb290Ci9LIFs5IDAgUl0KL1BhcmVudFRyZWUgMTAgMCBSCi9QYXJlbnRUcmVlTmV4dEtleSA3Cj4+CmVuZG9iago0IDAgb2JqCjw8Ci9NYXJrZWQgdHJ1ZQovU3VzcGVjdHMgZmFsc2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL1R5cGUgL1ZpZXdlclByZWZlcmVuY2VzCi9EaXNwbGF5RG9jVGl0bGUgdHJ1ZQo+PgplbmRvYmoKNyAwIG9iago8PAovVHlwZSAvUGFnZQovUmVzb3VyY2VzIDw8Ci9Qcm9jU2V0IFsvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJXQovRXh0R1N0YXRlIDExIDAgUgovRm9udCAxMiAwIFIKPj4KL01lZGlhQm94IFswLjAgOC4zNzAwMDMgMTQxLjc1IDI0OS4xMl0KL0Fubm90cyBbMTMgMCBSXQovQ29udGVudHMgMTQgMCBSCi9TdHJ1Y3RQYXJlbnRzIDAKL1BhcmVudCAyIDAgUgovVGFicyAvUwovQmxlZWRCb3ggWzAuMCA4LjM3MDAwMyAxNDEuNzUgMjQ5LjEyXQovVHJpbUJveCBbMC4wIDguMzcwMDAzIDE0MS43NSAyNDkuMTJdCi9Dcm9wQm94IFswLjAgOC4zNzAwMDMgMTQxLjc1IDI0OS4xMl0KL1JvdGF0ZSAwCj4+CmVuZG9iago4IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9SZXNvdXJjZXMgPDwKL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9FeHRHU3RhdGUgMTUgMCBSCi9Gb250IDE2IDAgUgo+PgovTWVkaWFCb3ggWzAuMCA4LjM3MDAwMyAxNDEuNzUgMjQ5LjEyXQovQW5ub3RzIFsxNyAwIFJdCi9Db250ZW50cyAxOCAwIFIKL1N0cnVjdFBhcmVudHMgMQovUGFyZW50IDIgMCBSCi9UYWJzIC9TCi9CbGVlZEJveCBbMC4wIDguMzcwMDAzIDE0MS43NSAyNDkuMTJdCi9UcmltQm94IFswLjAgOC4zNzAwMDMgMTQxLjc1IDI0OS4xMl0KL0Nyb3BCb3ggWzAuMCA4LjM3MDAwMyAxNDEuNzUgMjQ5LjEyXQovUm90YXRlIDAKPj4KZW5kb2JqCjkgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RvY3VtZW50Ci9QIDMgMCBSCi9LIFsxOSAwIFIgMjAgMCBSXQo+PgplbmRvYmoKMTAgMCBvYmoKPDwKL0xpbWl0cyBbMCAxXQovTnVtcyBbMCBbMjEgMCBSIDIyIDAgUiAyMyAwIFIgMjQgMCBSIDI1IDAgUl0KIDEgWzI2IDAgUiAyNyAwIFIgMjggMCBSIDI5IDAgUiAzMCAwIFIgMzEgMCBSIDMyIDAgUiAzMyAwIFJdCl0KPj4KZW5kb2JqCjExIDAgb2JqCjw8Ci9HMyAzNCAwIFIKL0c3IDM1IDAgUgo+PgplbmRvYmoKMTIgMCBvYmoKPDwKL0Y0IDM2IDAgUgovRjUgMzcgMCBSCi9GNiAzOCAwIFIKPj4KZW5kb2JqCjEzIDAgb2JqCjw8Ci9UeXBlIC9Bbm5vdAovU3VidHlwZSAvTGluawovRiA0Ci9Cb3JkZXIgWzAgMCAwXQovUmVjdCBbNDEuMDMxNDAzIDQyLjEwOTA4NSAxMDEuMDMxNDAzIDEwMi4xMDkwODVdCi9BIDM5IDAgUgo+PgplbmRvYmoKMTQgMCBvYmoKPDwKL0xlbmd0aCAxOTc5Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCj4+CnN0cmVhbQ0KeJztms+PHbcNx5se3yG99JDjnIK0gGdFSqSkwlgk\/pGkKGLEzgK+5JbWAVq7gNP\/Hyg1wy\/nvdn3sptbDs9r7D59RiIpksMnQZo59+XflOznyXzU5NJn4umnD4ePh\/FQepqVZaKUsj2Q6Zd\/Hd7+9fBfe7y2R6ftk42jafy8+cY\/\/PLz4eabPP38v0UctT5lpiHl3eG1\/
...
...
.../iiPE4+Sx9nZ+QBwpByBGMQeDyWUkOW+73xaODaCwpJ2Sn9Hvw4CaFStHDCVt0crtjmUUC+G7EFfCAmWUy3cLuvOxN6cSPz39+u9ssT0xye1Bu7uruLjLbecKuB3Vjf5zh4+c90eeO\/a9Nxu83gav3f7C8Ev3HX154pWLfXeZ2vUG24jLlbCVl3r5u\/vHo2daY383OvFUZy1ZG+ju9gc7u4LAnwCCC2JbDQplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA3MAowMDAwMDAwMDAwIDY1NTM1IGYNCjAwMDAwMDAwMTUgMDAwMDAgbg0KMDAwMDAwMDQyNCAwMDAwMCBuDQowMDAwMDAwNDg3IDAwMDAwIG4NCjAwMDAwMDA1ODEgMDAwMDAgbg0KMDAwMDAwMDYzMSAwMDAwMCBuDQowMDAwMDAwMTUyIDAwMDAwIG4NCjAwMDAwMDA2OTkgMDAwMDAgbg0KMDAwMDAwMTA2NCAwMDAwMCBuDQowMDAwMDAxNDI5IDAwMDAwIG4NCjAwMDAwMDE1MDkgMDAwMDAgbg0KMDAwMDAwMTY1NCAwMDAwMCBuDQowMDAwMDAxNjk4IDAwMDAwIG4NCjAwMDAwMDE3NTMgMDAwMDAgbg0KMDAwMDAwMTg4NCAwMDAwMCBuDQowMDAwMDAzOTM5IDAwMDAwIG4NCjAwMDAwMDM5ODMgMDAwMDAgbg0KMDAwMDAwNDAyNyAwMDAwMCBuDQowMDAwMDA0MTU3IDAwMDAwIG4NCjAwMDAwMDY2NTMgMDAwMDAgbg0KMDAwMDAwNjc2MSAwMDAwMCBuDQowMDAwMDA2ODkwIDAwMDAwIG4NCjAwMDAwMDcwMzYgMDAwMDAgbg0KMDAwMDAwNzEyOCAwMDAwMCBuDQowMDAwMDA3MzA2IDAwMDAwIG4NCjAwMDAwMDczNzcgMDAwMDAgbg0KMDAwMDAwNzQ3MSAwMDAwMCBuDQowMDAwMDA3NTY5IDAwMDAwIG4NCjAwMDAwMDc2NjAgMDAwMDAgbg0KMDAwMDAwNzc1MiAwMDAwMCBuDQowMDAwMDA3ODIzIDAwMDAwIG4NCjAwMDAwMDc5MjIgMDAwMDAgbg0KMDAwMDAwODAyNSAwMDAwMCBuDQowMDAwMDA4MTI2IDAwMDAwIG4NCjAwMDAwMDgyMjEgMDAwMDAgbg0KMDAwMDAwODI2MSAwMDAwMCBuDQowMDAwMDA4MzQwIDAwMDAwIG4NCjAwMDAwMDg0OTAgMDAwMDAgbg0KMDAwMDAwODY0MSAwMDAwMCBuDQowMDAwMDA4Nzg5IDAwMDAwIG4NCjAwMDAwMDg4NjYgMDAwMDAgbg0KMDAwMDAwODkwNiAwMDAwMCBuDQowMDAwMDA4OTg1IDAwMDAwIG4NCjAwMDAwMDkxMzMgMDAwMDAgbg0KMDAwMDAwOTI4NCAwMDAwMCBuDQowMDAwMDA5Mzg1IDAwMDAwIG4NCjAwMDAwMDk3MjEgMDAwMDAgbg0KMDAwMDAxMDEyMyAwMDAwMCBuDQowMDAwMDEwNDE2IDAwMDAwIG4NCjAwMDAwMTA3NzggMDAwMDAgbg0KMDAwMDAxMTAwMSAwMDAwMCBuDQowMDAwMDExMzMyIDAwMDAwIG4NCjAwMDAwMTE3MTcgMDAwMDAgbg0KMDAwMDAxMjEzMyAwMDAwMCBuDQowMDAwMDEyNjAzIDAwMDAwIG4NCjAwMDAwMTMwNjAgMDAwMDAgbg0KMDAwMDAxMzI2NCAwMDAwMCBuDQowMDAwMDEzMzM5IDAwMDAwIG4NCjAwMDAwMTM1NDQgMDAwMDAgbg0KMDAwMDAxMzYxOSAwMDAwMCBuDQowMDAwMDEzODIxIDAwMDAwIG4NCjAwMDAwMTM4OTYgMDAwMDAgbg0KMDAwMDAxNDA5OCAwMDAwMCBuDQowMDAwMDE0MTczIDAwMDAwIG4NCjAwMDAwMTQzNzggMDAwMDAgbg0KMDAwMDAxNDQ1MyAwMDAwMCBuDQowMDAwMDE2NDUxIDAwMDAwIG4NCjAwMDAwMTc5NzIgMDAwMDAgbg0KMDAwMDAxOTE3NyAwMDAwMCBuDQowMDAwMDIxNDYzIDAwMDAwIG4NCnRyYWlsZXIKPDwKL1Jvb3QgMSAwIFIKL0luZm8gNiAwIFIKL0lEIFs8RDQyNTNBNDBFOTIzMzdDMzAxRjJEQUE1MDU2QTE1NUI+IDxENDI1M0E0MEU5MjMzN0MzMDFGMkRBQTUwNTZBMTU1Qj5dCi9TaXplIDcwCj4+CnN0YXJ0eHJlZgoyNDYxMwolJUVPRgo=","directory":"DOCUMENTS","recursive":true}
[info][console] PDF saved to: file:///storage/emulated/0/Documents/MyPDFs/my-pdf.pdf
[error][console] FileOpener error: Error: "FileOpener" plugin is not implemented on android
[info][console] An error occurred while opening the PDF
I am not sure what I am doing wrong. This is my entire component. I would appreciate any insights on something I might have missed:
<!-- filepath: /home/arpit/Desktop/AI_Experiments/tlbot_website/frontend/src/components/PDFViewerModal.vue -->
<template>
<ion-modal :is-open="isOpen" swipeToClose="true" @ionModalDidDismiss="closeModal">
<ion-content>
<ion-button fill="clear" color="medium" @click="closeModal"
style="position:absolute; top:10px; right:10px; z-index:1000;">
Close
</ion-button>
<div v-if="embedForWeb" class="pdf-container">
<!-- Simple iframe embed for web browsers -->
<iframe v-if="pdfUrl" :src="pdfUrl" width="100%" height="100%"></iframe>
<div v-else class="no-pdf">No PDF URL provided</div>
</div>
</ion-content>
</ion-modal>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import { defineProps, defineEmits } from 'vue';
import { IonModal, IonContent, IonButton } from '@ionic/vue';
import { Capacitor } from '@capacitor/core';
import { FileOpener, FileOpenerOptions } from '@capacitor-community/file-opener';
// import { FileOpener } from '@capawesome-team/capacitor-file-opener';
import { Filesystem, Directory } from '@capacitor/filesystem';
import api from '@/services/api';
import { Toast } from '@capacitor/toast'; // Import Capacitor Toast
const props = defineProps({
isOpen: { type: Boolean, required: true },
pdfUrl: { type: String, default: '' }
});
const emit = defineEmits(['update:isOpen']);
function closeModal() {
emit('update:isOpen', false);
}
const embedForWeb = ref(true);
async function requestFSAccess() {
// Explicitly request storage permission if needed on Android
try {
await Filesystem.requestPermissions();
} catch (err) {
console.error('Could not request FS permission:', err);
}
}
async function openPDFWithFileOpener() {
if (!props.pdfUrl) return;
if (Capacitor.isNativePlatform()) {
// Request permission to store the file in Documents
await requestFSAccess();
try {
// 1. Fetch the PDF as a Blob
const response = await api.get(props.pdfUrl, {
responseType: 'blob',
});
const blob = response.data;
// 2. Convert Blob to Base64
const reader = new FileReader();
reader.onloadend = async () => {
const base64String = reader.result as string;
const base64Data = base64String.replace(/^data:application\/pdf;base64,/, '');
// 3. Write to Documents folder rather than Cache
// so you can see it in a file manager
const { uri } = await Filesystem.writeFile({
path: 'MyPDFs/my-pdf.pdf',
data: base64Data,
directory: Directory.Documents,
recursive: true,
});
console.log('PDF saved to:', uri);
// 4. Open the File: Native method using FileOpener
try {
const fileOpenerOptions: FileOpenerOptions = {
filePath: props.pdfUrl,
contentType: 'application/pdf',
openWithDefault: true
};
await FileOpener.open(fileOpenerOptions);
} catch (err: any) {
console.error('FileOpener error:', err);
// Handle the error gracefully
if (err.message === 'No Activity Found') {
// Display a message to the user that no app is available to open the PDF
console.log('No app found to open PDF');
} else {
// Handle other errors
console.log('An error occurred while opening the PDF');
}
}
// 5. Alternative method to open the file using capawesome
// try {
// await FileOpener.openFile({
// path: uri,
// contentType: 'application/pdf',
// });
// console.log('File opened');
// } catch (fileOpenerError: any) {
// console.error('FileOpener error:', fileOpenerError);
// // Catch and ignore "not implemented" error
// if (fileOpenerError.message.includes('not implemented on android')) {
// console.warn('FileOpener not implemented on Android, but PDF saved.');
// // Show notification to user
// await Toast.show({
// text: 'PDF saved to Files folder. Please check your device file manager.',
// duration: 'long',
// });
// } else {
// // Handle other FileOpener errors
// console.error('Error opening PDF:', fileOpenerError);
// }
// }
};
reader.onerror = (error) => {
console.error('Error reading blob:', error);
};
reader.readAsDataURL(blob);
} catch (err: any) {
console.error('FileOpener error:', err);
}
} else {
// Handle web-based PDF viewing
window.open(props.pdfUrl, '_blank');
}
}
function openPDFWeb() {
// This allows the browser to open the PDF in a new tab
window.open(props.pdfUrl, '_blank');
}
watch(
() => props.isOpen,
async (newVal) => {
if (newVal) {
if (Capacitor.isNativePlatform()) {
console.log('Opening PDF with FileOpener');
// For Android/iOS, open the file externally
embedForWeb.value = false;
await openPDFWithFileOpener();
// Immediately close the modal after launching the external viewer,
// or remove this line if you want the modal to remain visible.
closeModal();
} else {
// For web, just show the iframe
// embedForWeb.value = true;
// web
console.log('Opening PDF for web');
embedForWeb.value = false; // Not showing the iframe anymore
openPDFWeb();
closeModal();
}
}
}
);
onMounted(() => {
// Decide whether to embed or use FileOpener on mount
if (Capacitor.isNativePlatform()) {
embedForWeb.value = false;
}
});
</script>
<style scoped>
.pdf-container {
position: relative;
width: 100%;
height: 100vh;
/* Adjust as needed */
overflow: hidden;
}
.pdf-container iframe {
border: none;
}
.no-pdf {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-weight: 500;
color: #666;
}
</style>