I have an Ionic 4 app developed using InAppBrowser. The app is, in fact, almost all purely done via InAppBrowser:
Splash Screen > URL passed to InAppBrowser > Site loads.
User logins in, clicks links and navigates through site.
Some links the user may click are PDFS (content type = application/pdf) but does not have a .pdf extension at the end of the filename.
In iOS, these load fine. In Android, the PDFs never show....
I've read this is a known workaround. I cannot use the Google Docs viewer embed workaround as the URL to the pdf files are based on being authenticated into our site to see and access the PDFs.
The other option I'm reading is to get the URL, download the file to the user's local system browser; then open it up - all of this using FileSystem, File, FileTransfer, and FileOpener. Below is my code block and output from Android Studio LOGCAT:
downloadPDF(pUrl: string) {
console.log("==== in downloadOpenPDF function ====");
if(this.platform.is('android')) {
var permissions = cordova.plugins.permissions;
permissions.hasPermission(permissions.WRITE_EXTERNAL_STORAGE , function( status ){
if ( status.hasPermission ) {
console.log("We DO have access to write to external storage");
}
else {
console.warn("We DO NOT have access to write to external storage");
permissions.requestPermission(permissions.WRITE_EXTERNAL_STORAGE, this.success, this.error);
}
});
permissions.hasPermission(permissions.READ_EXTERNAL_STORAGE , function( status ){
if ( status.hasPermission ) {
console.log("We DO have access to read to external storage");
}
else {
console.warn("We DO NOT have access to read to external storage");
permissions.requestPermission(permissions.READ_EXTERNAL_STORAGE, this.success, this.error);
}
});
}
let downloadUrl = pUrl;
console.log("downloadUrl is ================"+downloadUrl);
let path = this.file.dataDirectory;
let filepath = path+'mcfrenderPDF.pdf';
console.log("path is =================="+path);
const transfer: FileTransferObject = this.fileTransfer.create();
transfer.download(downloadUrl, filepath,true).then(entry => {
let url = entry.toUrl();
console.log("url is ================="+url);
if(this.platform.is('ios') || this.platform.is('android')) {
console.log("================ Use Document Viewer");
this.document.viewDocument(url, 'application/pdf',{});
} else {
//console.log("Device is Android = Use Google Docs");
//this.openGoogleDocsPDF(pUrl);
console.log("local path to file is: "+path+'mcfrenderPDF.podf');
this.fileOpener.open(entry.URL, 'application/pdf');
}
})
}
My output of my log is as follows:
2019-11-03 13:26:05.401 5213-5333/com.cat.fpd.mobile.mcf D/FileTransfer: Saved file: file:///data/user/0/com.cat.fpd.mobile.mcf/files/mcfrenderPDF.pdf
2019-11-03 13:26:05.401 5213-5333/com.cat.fpd.mobile.mcf E/FileTransfer: File plugin cannot represent download path
Related
I have a small MAUI app i'm testing with. Im trying to read a file that was part of the deployment. I have the code below, which works great in a Windows deploy of the MAUI app, but crashes in Android. What is the proper cross-platform way to do this?
// TODO get from service or xml
var path = AppDomain.CurrentDomain.BaseDirectory;
//var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var fullpath = Path.Combine(path, "Services\\questions.json");
var json = File.ReadAllText(fullpath);
MAUI has a new way to access files included with the app: MauiAsset.
Described in blog Announcing .NET MAUI Preview 4, Raw Assets:
.NET MAUI now makes it very easy to add other assets to your project and reference them directly while retaining platform-native performance. For example, if you want to display a static HTML file in a WebView you can add the file to your project and annotate it as a MauiAsset in the properties.
<MauiAsset Include="Resources\Raw\index.html" />
Tip: you can also use wildcards to enable all files in a directory:
... Include="Resources\Raw\*" ...
Then you can use it in your application by filename.
<WebView Source="index.html" />
UPDATE
However, the feature MauiAsset apparently still needs improvement:
open issue - MauiAsset is very hard to use.
There we learn that for now:
Set BuildAction in each file's properties to MauiAsset.
That is, its not recommended to use the "wildcard" approach at this time. Set that build action on each file in solution explorer / your project / the file.
Accessing on Windows requires a work-around:
#if WINDOWS
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync("Assets/" + filePath);
#else
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync(filePath);
#endif
NOTE: This will be simplified at some point; follow that issue to see progress.
UPDATE
The current MAUI template is missing some platform-specific flags. For now, add your own flag to identify when the code is running on Windows:
Complete example in ToolmakerSteve - repo MauiSOAnswers. See MauiAssetPage.
MauiAssetPage.xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTests.MauiAssetPage">
<ContentPage.Content>
<!-- By the time Maui is released, this is all you will need. -->
<!-- The Init code-behind won't be needed. -->
<WebView x:Name="MyWebView" Source="TestWeb.html" />
</ContentPage.Content>
</ContentPage>
MauiAssetPage.xaml.cs:
using Microsoft.Maui.Controls;
using System.Threading.Tasks;
namespace MauiTests
{
public partial class MauiAssetPage : ContentPage
{
public MauiAssetPage()
{
InitializeComponent();
Device.BeginInvokeOnMainThread(async () =>
{
await InitAsync();
});
}
private async Task InitAsync()
{
string filePath = "TestWeb.html";
#if WINDOWS
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync("Assets/" + filePath);
#else
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync(filePath);
#endif
if (stream != null)
{
string s = (new System.IO.StreamReader(stream)).ReadToEnd();
this.MyWebView.Source = new HtmlWebViewSource { Html = s };
}
}
}
}
TestWeb.html:
(whatever html you want)
In Solution Explorer, add TestWeb.html to your project. In its Properties, select Build Action = MauiAsset.
I tried looking for a solution to this for months. I ended up hosting the file online then creating a method to download the file during runtime
public async Task DownloadFile(string fileName)
{
if (File.Exists(FileSystem.Current.AppDataDirectory + $"/{fileName}"))
{
return;
}
else
{
try
{
NetworkAccess networkAccess = Connectivity.Current.NetworkAccess;
if (networkAccess == NetworkAccess.Internet)
{
await Task.Run(() =>
{
var uri = new Uri($"https://myhostedfile.com/{fileName}");
WebClient webClient = new WebClient();
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCallback2);//checking if download is complete
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MaintainProgress);//event handler to check download progress
webClient.DownloadFileAsync(uri, FileSystem.Current.AppDataDirectory + $"/{fileName}");
});
}
else
await Shell.Current.DisplayAlert("No Internet", "Failed to get some files from the internet, confirm if your internet is" +
"working", "OK");
}
catch (Exception)
{
await Shell.Current.DisplayAlert("Error", "Failed to get some files from the internet, confirm if your internet is" +
"working", "OK");
}
}
}
Then you can access your file URL using:
string filePath = FileSystem.Current.AppDataDirectory + $"/myfile.pdf;
I have built a FTP Server in android app using https://mina.apache.org/ftpserver-project/documentation.html.
I need to render a html page from Assets as my home page. But it is always listing a directory.
connectionConfigFactory.isAnonymousLoginEnabled = true
connectionConfigFactory.maxLogins = 1
connectionConfigFactory.maxThreads = 1
ftpServerFactory.connectionConfig = connectionConfigFactory.createConnectionConfig()
ftpServer = ftpServerFactory.createServer()
listenerFactory.setPort(2121)
ftpServerFactory.addListener("default", listenerFactory.createListener())
ftpServerFactory.ftplets.put(FTPLetImpl::class.java.getName(), FTPLetImpl())
val files =
File(Environment.getExternalStorageDirectory().path + "/users.properties")
if (!files.exists()) {
try {
files.createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}
}
userManagerFactory.setFile(files)
userManagerFactory.setPasswordEncryptor(SaltedPasswordEncryptor())
val um: UserManager = userManagerFactory.createUserManager()
val user = BaseUser()
user.name = "anonymous"
user.enabled = true
val home =
Environment.getExternalStorageDirectory().path + "/Test"
user.homeDirectory = home
val auths: MutableList<Authority> =
ArrayList()
val auth: Authority = WritePermission()
val auth1: Authority = ConcurrentLoginPermission(MAX_CONCURRENT_LOGINS,MAX_CONCURRENT_LOGINS_PER_IP)
val auth2: Authority = TransferRatePermission(Integer.MAX_VALUE, Integer.MAX_VALUE)
auths.add(auth)
auths.add(auth1)
auths.add(auth2)
user.authorities = auths
try {
ftpServerFactory.userManager.save(user)
} catch (e1: FtpException) {
e1.printStackTrace()
}
Even though I set the user home directory as Html page. It gets downloaded in the browser & not rendering it
It's an FTP server. So when an FTP client asks for a directory listing, it gets textual (in case of LIST) or even structured (in case of MLSD) information about files in the directory.
And it's up to the FTP client, how it displays that information to the user. You have no control over that. It's irrelevant that in your particular case, the FTP client is primarily a web browser. In this context, it is not a web browser, it is an FTP client.
If you want to produce an HTML page, you need to implement an HTTP server, not an FTP server.
I am using sqlflite flutter package to manage a small database for my app, there is a table where I have some urls for few images from the internet , but I need to save them and use the files instead of urls to show this images later. I mean I want to do something like this :
bool internet
internet ? Image.network('the_url') : Image.assets("path to image in my assets folder")
so, is there any way to fetch images from urls and save it to be able to access it later?
You can download the image with NetworkAssetBundle and convert to Unint8List
final ByteData imageData = await NetworkAssetBundle(Uri.parse("YOUR_URL")).load("");
final Uint8List bytes = imageData.buffer.asUint8List();
Then you can load it through Image.memory() widget
Image.memory(bytes);
You can store that data in sqflite and retrive when needed
You can use the path_provider package to download from internet and save it locally. Then you can load from local asset if available. Otherwise it can download from the internet also.
Future<String?> loadPath() async {
var dir = await getApplicationDocumentsDirectory();
String dirName = widget.url!.substring(87);
file = File("${dir.path}/$dirName");
if (file.existsSync() == true && _isDownloadingPDF == false) {
// debugPrint('file.path returned');
return file.path;
} else {
return null;
}
}
Have you tried https://pub.dev/packages/cached_network_image?
It seems like this package will fulfil your requirements.
I am using ionic 3.13.1 framework. My application should allow user to work with files from device and from google drive. I am using IonicNativeFile and IonicNativeFileChooser. So, when I select file from device memory - all works good, but I cannot use that method for google drive files. My function looks like:
public OpenFile(event: Event) {
this.fileChooser.open() //Open File Chooser
.then(uri => { //get uri
console.log(`URI: ${uri}`);
( < any > window).FilePath.resolveNativePath(uri, (result) => { //Get file path in storage from uri
this.nativepath = result;
console.log(`Native Path: ${this.nativepath}`);
this.readFile(); //read data from file
this.loader = this.loadingCtrl.create({ //Create and show loader message
content: "Loading..."
});
this.loader.present();
}, (error) => {
console.log(`Looks like file from GDrive: code ${error.code} message ${error.message}`);
});
}).catch(e => console.log(e));}
The uri is: content://com.google.android.apps.docs.storage.legacy/enc%3Di2Alapnkehx4uFSbuS2U3VO_rC-nrHu3Emq8u8eF4Z9w8QvL%0A
ResolveNativePath shoud return 1 if file is in cloud, but my function returns 0 with message: Unable to resolve filesystem path. I dont't know how to fix it.
Help me, please. What should I do to get path of file?
I am attempting to open a PDF file with FileOpener2 (through ng-cordova) with the following code:
$cordovaFile.checkFile(cordova.file.dataDirectory, attachmentPath)
.then((fileEntry) => {
// success
fileEntry.getMetadata((metadata) => {
// metadata.size is in bytes
var megabyteSize = metadata.size / 1048576;
if (megabyteSize > 5) {
var path = cordova.file.dataDirectory + attachmentPath;
console.log(path); // prints: file:///data/data/com.ionicframework.enhatch146189/files/attachments/CS-353ES_CS-420ES_Eng.pdf which is correct
$cordovaFileOpener2.open(path, 'application/pdf').then(() => {
console.log("Opened!") // prints
}, (error) => {
console.log(error);
usePDFJs(); // tries to render PDF in app with PDFJs
});
} else {
usePDFJs();
}
})
}, function (error) {
// error
console.error(error);
});
What happens confuses me: it prompts me with an "open this file in Adobe Reader?" and lists the other PDF viewers, and the console prints "Opened!"
However, no matter what I open ANY pdf in, I get some sort of error such as "cannot open this PDF file".
Can anyone see something wrong with this code?
Apparently, if you use cordova.file.dataDirectory on android you can't open those files in other applications or attach them to emails. Silly mistake -- coded too fast and read too little on the documentation. Using cordova.file.externalApplicationStorageDirectory solved the issue.