Downloading large pdf files in a Flutter app - dart

I'm looking for a way to download large pdf files from an external server with a Flutter application for offline storage.
But downloading a large file (sometimes 100mb+) takes some time. I don't want the app being stuck in a wait function for it to download. What i'm looking for is a download function that has a callback with a progress report (Something like: 250000/500000 bytes done. Doesn't have to be exactly that. Just something that I can work with and make a progress bar out of).
Is this even possible to do in Flutter? The only things I came across were the HTTP library. But that does not seem to have a progress callback and just plainly reading the contents of a http call (Which also doesn't have a progress report). I hope someone has a method for me that I can use to make this happen.
Kind regards,
Kevin Walter
EDIT:
C# has the perfect example of what I mean
https://stackoverflow.com/a/9459441/2854656

https://docs.flutter.io/flutter/dart-io/HttpClient-class.html
https://docs.flutter.io/flutter/dart-io/HttpClientResponse-class.html
int fileSize;
int downloadProgress = 0;
new HttpClient().get('localhost', 80, '/file.txt')
.then((HttpClientRequest request) => request.close())
.then((HttpClientResponse response) {
fileSize ??= respone.contentLength;
response.transform(utf8.decoder).listen((contents) {
downloadProgres += contents.length;
// handle data
});
});

Related

React Native HTML to PDF not displaying local images

In case you guys don't know, there was a problem previously with this library not rendering local images on Android as well, but apparently it was solved. Now, I'm facing the exact same issue on iOS, with a difference that I can use static images like assets/src/assets/images/logo.png. But when the images start with something like file:///, storage://, ph:// it simply does not get rendered.
What I'm doing is trying to generate a PDF report file, which must be generated independently the user has an internet connection or not. That is the reason why I have to use local images.
The static image is the logo of the company, and the local image which is not getting rendered is an image saved to the phone's storage through Image Picker or Camera Roll. The React Native Image component displays the image perfectly, so I don't think I'm using a wrong path.
What I have tried so far:
Removing the file:/// or storage:// or ph:// from the beginning of the path string;
In some cases, when I save an image to the phone's library with Camera Roll, it will return a path that starts with ph:// but without an extension, such as .jpg or .png. I tried to put the extension manually, and still does not make any difference;
I tried to convert the image to base64 using rn-fetch-blob (with RNFetchBlob.fs.base64.encode(path)), but still got no success.
Devices:
iPhone SE with iOS 14 (also simulator iPhone 11 with iOS 15)
MacBook Air 2017 Core i5 1.8GHz and 8gb RAM (macOS Big Sur 11.5.2)
Environment
node: 12.22.7
npm: 6.14.15
react: 16.9.0
react-native: 0.61.5
react-native-html-to-pdf: ^0.11.0 (updating it to 0.12.0 also got me the same result)
Code:
sharePDF = async () => {
try {
this.changeVisibilityOptions(false);
this.changeVisibilityLoading('Gerando PDF...');
let htmlTemplate = '';
htmlTemplate = await getPDFDespesa(this.state);
const pdfOptions = {
html: htmlTemplate,
fileName: 'RelatorioDespesas',
directory: 'Relatorios'
};
let pdfFile = await RNHTMLtoPDF.convert(pdfOptions);
this.changeVisibilityLoading(false);
const shareOptions = {
title: 'Compartilhar com:',
url: `file://${pdfFile.filePath}`,
type: 'application/pdf',
failOnCancel: false
};
const ShareResponse = await Share.open(shareOptions);
} catch (error) {
this.setState({ visibilityLoadingScreen: false });
console.log('Error =>', error);
}
}
Final thoughts:
Well, since the code is stored at a private repository, I can't show the whole thing here for ethical reasons. But I'm doing my best to give you as much details as possible.
The output the code produces an almost complete PDF, with the only point that I see broken image icons where the images were supposed to be. For Android it works perfectly now.
I think this might be an issue related to WebView, since react-native-html-to-pdf uses WebView to generate the PDF from HTML code. I reached this conclusion after another developer at my job was trying to create a screen with a preview of the PDF before it could be shared got the very same problem for both Android and iOS. The library he used was react-native-webview.
Update with solution
Alright guys, after a long time of research, me and a colleague got to a solution which may not be the best but does what we expected.
First of all, one thing that was discovered is that we have to divide the problem in two, because we actually had two problems.
Images from react-native-image-picker: After a long time trying to find the problem which was preventing the local images from getting rendered, I tried updating the library to version 4.7.3 (latest version at that day) and did a number of required changes to the code, as the version we were using was considerably aged. Well, it happened to work out for my surprise, even with the response uri's format not being changed;
Images from #react-native-community/cameraroll: This one was a bit more complicated. It took me some time to realize that the iOS' PHAsset was not supported in the WebView or react-native-html-to-pdf (which uses WebView in background). So, after some research, me and my colleague found a workaround that lead us to a relatively easy solution. Basically we used react-native-fs to copy the PHAsset media file to a temporary directory, which would return a uri that started with file:// and could be rendered by WebView. That's the code we used to do this:
export default function getImageNameFromUrl(imageUrl = "") {
if (imageUrl) {
const splittedImageUrl = imageUrl.split('/');
return splittedImageUrl.pop();
}
return null;
};
export default async function copyAssetsFileIOSAndReturnURI(remoteURL = '', localURI = '') {
try {
if (remoteURL && localURI) {
const imageName = getImageNameFromUrl(remoteURL);
const imgPath = await RNFS.copyAssetsFileIOS(localURI, RNFS.TemporaryDirectoryPath+imageName, 0, 0);
return imgPath;
}
return null;
} catch (err) {
console.log(err);
return null;
}
}

iOS Safari issue with Audio from Server

So currently working on a project and experiencing a strange issue with the ios version of Safari involving the playback of an audio file from a server.
I'm currently facing the following issue:
Person comes onto the page which has a standard html5 audio tag, and a direct link to the audio file for downloading purposes.
Person tries to listen to audio from audio tag, content plays for x number of minutes, cuts off then repeats (the x number of minutes is NOT the length of the recording, and is not consistent).
Person tries direct link of recording, rather then downloading the recording, Safari appears to go to a new page and wraps the download url in a video element, and the same issue as step 2 occurs.
Now the audio file is served up via a java scriptlet, which serves the file with the following code snippet:
String fn = saveTo + file_name;
f = new File(fn);
String fname = f.getName();
String contentType = "audio/wav";
if(fname.endsWith("mp3")){
contentType = "audio/mp3";
}
response.setContentType(contentType);
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-disposition", "attachment;filename="+f.getName());
response.setHeader("Content-Length", ""+f.length());
FileInputStream fin = null;
try{
fin = new FileInputStream(f.getCanonicalFile());
byte[] data = new byte[1024];
int x = 0;
while((x = fin.read(data, 0, 1024))>=0){
response.getOutputStream().write(data, 0, x);
Thread.sleep(1);
}
} finally {
if(fin != null) {
try{
fin.close();
}catch(Exception ex){}
}
}
Now I know the code isn't the best by any measure, it isn't my code, and we're obviously working on the assumption that the file is found.
I'm finding when debugging on the iPhone with debug mode on a mac, it doesn't seem to show a return status code. It shows no response headers but it obviously must be receiving something. The server log seems to think its returning a status 200, this showing in Chrome and Firefox.
The code above appears to work fine with Chrome, and Firefox, but not Safari.
The only thing I am guessing is it has something to do with how the file is being pushed to the output stream that Safari isn't liking, or maybe its getting confused and should have a different status code. I've been banging my head against this for a good few days, and reading as much as I can about Safari, though most of the documentation I'm finding is on its "unique" implementation of web audio, and the use of a single channel which seems to be irrelevant to this.
Any Help would be appreciated.
I experienced the same issue with Safari on iOS, and after a lot of debugging, I found the issue was related to the combination of headers applied to the response.
My application is C#-based, but this solution should be platform independent (because as previously stated, it is a response header issue).
Necessary Headers:
Content-Range: bytes 0-[content length]/[content length]
Content-Transfer-Encoding: binary
Content-Length: [content length]
Accept-Ranges: bytes
I devised this after inspecting a response from MP3s delivered via Akamai's content delivery service.

Is it possible to save a mp3 file downloaded from server without converting it to ByteArray? (AS3)

After searching around the net, I couldn't find a method to download a mp3 from a server, save it in the local storage (iPad) and then load and play it other than converting it to byteArray, saving it as .mp3 and then load it and read it back to mp3 to be able to play it in the flash application.
The problem is, although this method works fine, the uncompressed files (in byteArray format) saved in the local storage are too heavy and I suspect that the app is wasting memory.
My question is, is there any form of saving the mp3 directly, without any conversion, like a properly playable mp3? I can't use methods like download() or save() from FileReference.
Lots of thanks!!
SOLUTION!! (WORKING PERFECTLY):
I continued looking for a method to do it around the net, and some forums gave me a clue to do whick I was searching. It was finally pretty simple, but I spent almost 2 weeks to find it out... here is my code:
var queue:LoaderMax = new LoaderMax({name:"mainQueue", onProgress:progressHandler, onComplete:completeHandler, onError:errorHandler});
queue.append( new DataLoader("http://" + **url**, {name:**"example.mp3"**format:"binay"}));
queue.load();
// Complete Event:
private function completeHandler():void {
var file:File = new File(**your location**); // appData
var fr:FileStream = new FileStream();
fr.open(file.resolvePath(nameIn), FileMode.WRITE);
fr.writeObject(LoaderMax.getContent("example.mp3"));
fr.close();
fr = null;
// Now the mp3 is saved in local storage, we load it as Sound object so we can play it.
var loader:MP3Loader;
loader = new MP3Loader(**your location**, {autoPlay:false}) ;
loader.addEventListener(Event.COMPLETE, function():Sound {
loader.content.play(); // This line is the only one not checked, but I am completely sure you can do something like this to play the sound. For example, I introduce the loader.content in a Array of Sound and later I am capable to play any sound I want.
loader.dispose(true); // This is very important to free memory. You should do the same thing with queue when all items are downloaded.
});
loader.load();
}
I expect this help a lot of people!!

Azure Storage api doesn't work for asnyc uploads

I am trying to upload so many files via Azure Blob Storage .NET api and using with the current latest version 4.0.1. In ASP.NET MVC application i use async action method to upload via await blobFile.UploadFromStreamAsync but it really doesn't work and even i don't see an exception. It silently stops in that method without success.
But if i change action method to none-async and upload via blobFile.UploadFromStream method then everything to works well. I may uploaded via async way with 1% success rate that means very very low stability.
Do you experience same thing ? Is it bug in Storage Api implementation ?
Here is short example. One is async and the other one is none async action methods. There is no any problem if i upload small files but problem appears on large downloads. In this example UploadBlobSec method upload in short time but UploadBlob takes endless time.
public async Task UploadBlob()
{
var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["AzureStorage"].ConnectionString);
var blobContainer = storageAccount.CreateCloudBlobClient().GetContainerReference("files");
var blobFile = blobContainer.GetBlockBlobReference("song.mp3");
using (var stream = new WebClient().OpenRead("http://apolyonstorage.blob.core.windows.net/files/e8b1a1fa-8791-44dc-92ce-1a67a62f7b0f.mp3"))
{
await blobFile.UploadFromStreamAsync(stream);
}
}
public void UploadBlobSec()
{
var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["AzureStorage"].ConnectionString);
var blobContainer = storageAccount.CreateCloudBlobClient().GetContainerReference("files");
var blobFile = blobContainer.GetBlockBlobReference("song.mp3");
using (var stream = new WebClient().OpenRead("http://apolyonstorage.blob.core.windows.net/files/e8b1a1fa-8791-44dc-92ce-1a67a62f7b0f.mp3"))
{
blobFile.UploadFromStream(stream);
}
}
Code snippet looks fine - although I am not sure what the application around it is doing. Have you turned on server logs to see what is happening server side? For large files you should see the async upload translated into a couple of PutBlocks and then a PutBlockList. If you don't see the PutBlockList them maybe something strange is happening in your application.
Then assuming you do see the operations in the server logs that obviously means the operations are occurring. At that point look at the E2ELatency numbers vs ServerLatency I think you will see a large difference with E2Latency being much higher as it incorporates the time spent client side - and it would be interesting to see if your client network could be contributing to the problem. For example on my connection the e2elatency on the first PutBlock was 1346 ms vs 137 for ServerLatency.
For more information on logging take a look here.
Jason

How to check for slow/low network in ios app

Can anyone suggest how to handle a slow network when streaming video in a web view?
When the network strength is poor, a blank screen appears or video doesn't stream.
Is there a way to detect this condition so that we can alert the user? (Apart from using private API.)
Perhaps ifi_baudrate member of the if_data structure (declared in <net/if.h>) is what you need. If baudrate is less than some threshold value, then you can show an alert.
Please see the following answer to know how to obtain the if_data structure for a particular network interface:
https://stackoverflow.com/a/8014012/1310204
You can easily detect the state of the network connection via the HTML5 networking API
http://www.html5rocks.com/en/mobile/optimization-and-performance/#toc-network-detection
Also if you want to test the network speed, just set up some files on your server of a specific size, and do a ajax request for the file, while timing how long it takes to download.
You can use a simple:
var start = new Date();
$.get("someFile.jpg")
.done(function() {
var elapsed = (new Date() - start);
});
Or dig into the HTML5 performance API:
http://www.html5rocks.com/en/tutorials/webperformance/basics/
...if you not using javascript, the same applies. Just open a network connection with whatever is at your disposition, download a small file & do the math ;-)

Resources