Background AsyncTask Takes to long to fullfill - android

I'm developing an Android App (Android 6.0 and above) and I have performance issues with large XML-Files. (about 17 mB)
When the app is started the required XML-document is loaded from the private storage and a List is returned (takes about 2-3 sek.) and filled in an custom adapter -> this works perfectly fine.
But the user can start a synchronization manually inside the app (e.g.: data
was updated on server)
Therefor I've implemented a background download-service so that the UI stays responsive during the download.
The downloaded data is stored inside the private folder again.
problem:
The background download works perfectly and my UI stays responive until I start reading the information from the new XML-File.
I don't get any result back -> even after 3 min there is no return value from the function although I use the same function for reading the XML like I do at the beginning -> GetKontaktliste()
public class MainActivity : AppCompatActivity
{
alert.SetPositiveButton("Synchronisieren", async (senderAlert, args) =>
{ new DownloadTask(this).Execute("");});
}
public class DownloadTask : AsyncTask
{
protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] #params)
{
App_Tools lAppTools = new App_Tools();
ThreadPool.QueueUserWorkItem(async state =>
{
//Download Function -> received XML-Data is saved in local storage
bool lKontakte = await lAppTools.DownloadKontakte(_context);
bool lVorgaenge = await lAppTools.DownloadVorgaenge(_context);
bool lVorgaengeImport = await lAppTools.ImportVorgaenge(_context);
if (lKontakte == true)
{
//Problem is that i dont get any results back here
List<KontaktItem> lResult = new List<KontaktItem>();
LinqAbfragen lLinq = new LinqAbfragen();
lLinq.GetKontaktliste();
}
});
return true;
}
}
public class LinqAbfragen
{
//Read Contacts
public List<KontaktItem> GetKontaktliste()
{
List<KontaktItem> lResult = new List<KontaktItem>();
//Read from IEnumerable<XElement>
var KontakteAsXElement = ReadXmlAsXElement("Kontakte.xml", "Kontakt");
lResult = (from kontakt in KontakteAsXElement
select new KontaktItem
{
AdressNr = kontakt.Element("AdressNr").Value,
Vorname = kontakt.Element("Vorname1").Value,
Nachname = kontakt.Element("Name1").Value,
xmlData = (string)kontakt.ToString()
}
).ToList();
return lResult;
}
}

Related

Flutter UI is still freezing when using isolate

I am developing an application in Flutter where I need to implement an image selection function like in instagram.
But there is an issue, my app UI is freezing when trying to get and compress files from user phone gallery.
This is my first experience with flutter isolates, but as far as i know it should work without freezes.
Here is an image for a better understanding of what i want to do.
This is a function that calls getFiles function in isolation.
Here i get paths of user phone gallery files and pass them to another function in order to compress and get files for rendering.
Future fetchImages({ bool fetchMore = false, bool force = false }) async {
if (!fetchMore) {
setState(() => fetched = false);
}
if (force) {
assetsCount = await assetPathEntity!.assetCountAsync;
page = 0;
files.clear();
}
if (assetsCount == 0 || page >= (assetsCount / pageSize)) {
return setState(() => fetched = true);
}
final assetEntities = await assetPathEntity!.getAssetListPaged(page: page++, size: pageSize);
lastCompletedIndex = files.length;
final receivePort = ReceivePort();
final completer = Completer();
getFiles(receivePort.sendPort, assetEntities);
try {
receivePort.listen((filesPaths) {
for (final filePath in filesPaths) {
files.add({
"path": filePath,
"compressedFile": null,
});
}
if (scaledFile == null && files.isNotEmpty) {
scaledFile = File(files.first["path"]);
}
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
}).onError((_) {
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
});
await Future.wait([completer.future]);
} catch (_) { }
finally {
receivePort.close();
}
}
This is getFiles function that runs in isolation
void getFiles(SendPort sendPort, List<AssetEntity> assetEntities) async {
final List<String> filesPaths = [];
for (final assetEntity in assetEntities) {
try {
final file = await assetEntity.file;
if (file != null) {
filesPaths.add(file.path);
}
} catch (_) { }
}
sendPort.send(filesPaths);
}
This is a function that calls compressImages function and adds any value to refresh the list of images
Here i pass the paths and get compressed files for rendering.
void compressAlbumImages() async {
final receivePort = ReceivePort();
final completer = Completer();
compressImages(receivePort.sendPort, files, lastCompletedIndex);
try {
receivePort.listen((compressedFilesWithPath) {
files = compressedFilesWithPath;
fileStreamCt.sink.add(1);
completer.complete();
}).onError((_) {
completer.complete();
});
await Future.wait([completer.future]);
} catch (_) {}
finally {
receivePort.close();
}
return;
}
This is an image compression function that runs in isolation
void compressImages(SendPort sendPort, List<Map<String, dynamic>> files, int startFromIndex) async {
final List<String> filesToBeRemoved = [];
for (int idx = startFromIndex; idx < files.length; idx++) {
final file = files[idx];
try {
final compressedFile = await FlutterNativeImage.compressImage(
file["path"],
quality: 20,
percentage: 20,
targetHeight: 300,
targetWidth: 300,
);
file["compressedFile"] = compressedFile;
} catch (_) {
filesToBeRemoved.add(file["path"]);
}
}
if (filesToBeRemoved.isEmpty) {
return sendPort.send(files);
}
final compressedFilesWithPaths = files
.whereNot((element) => filesToBeRemoved.contains(element["path"]))
.toList();
sendPort.send(compressedFilesWithPaths);
}
And finally i render compressed images
return StreamBuilder(
stream: fileStreamCt.stream,
builder: (ctx, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("LOADING...");
}
if (snapshot.hasError) {
return Text("AN ERROR OCCURRED");
}
return buildAlbumImages();
}
);
Render like this
Image.file(compressedImage);
If in short - When i'm getting user phone gallery files && compress them, my app UI starts freezing.
I don't know why it's freezing.
I tried the same process but with file.readAsBytes() and to render like Image.memory(compressedFileBytes), but it was useless.
I would be very grateful for any help.
Thanks in advance.
In your code you never actually create an isolate. The 'isolate code' in getFile, compressImages etc simply runs on the main isolate and indeed will block the UI.
Per documentation, you create an isolate with Isolate.spawn and pass only the sendPort. The isolate then must send back a receivePort, and the main thread uses that port to send the data you want to isolate to process (like assetEntities), processes it and sends the results back to the main thread. It's a bit complicated, and requires different function signatures than you have here.
Fortunately, a much easier way to accomplish what you want (still using Isolates that won't block the UI) is to use the compute function from the dart:async package:
Change the signature of your getFiles function to Future<List<String>> getFiles(List<AssetEntity> assetEntities) async and do in it what you need to do, returning the list of filesPaths as you do now. Importantly, getFiles must be a top level or a static function, it cannot be a regular class method. Then, where you need the calculation done you use something like var filesPaths = await compute(getFiles, assetEntities). Now, the getFiles function is called in an isolate, and the return value is given back to you on the main isolate. The nice thing is that now this looks a lot like regular await call, no need for sendPorts etc. You can do the same thing for your other heavy calculation methods.
One (big) constraint with isolates is the type of argument you can pass to and from an isolate, see here. Those same constraints apply here, because under the hood the compute function also uses sendPorts etc.

Xamarin.Forms Webview: ERR_CONNECTION_REFUSED trying to load local server with EmbedIO

I am trying run a local server for a Xamarin.Forms WebView. This is to get around CORS, and so the html can be structured like a normal page. This works for UWP and iOS, but Android always comes up with an ERR_CONNECTION_REFUSED. Some further details/things I have tried:
The App is running it's own server, so it is not the case of trying to access a server on a separate device.
Internet permission is enabled.
The path to the files do exist, otherwise the Webserver would fail to start.
Link to the local server nuget package: https://github.com/unosquare/embedio
Below is an outline of the code I'm using. In practise, I'm using a custom renderer, injecting Javascript to access platform features, etc. but this should simplify it:
The class that creates and starts the WebServer with EmbedIO:
public class LocalWebServer: IDisposable
{
public static string Url = "http://localhost:8787/";
private readonly string _filePath;
private WebServer _server;
public LocalWebServer(string filePath)
{
_filePath = filePath;
}
public void StartWebServer()
{
_server = new WebServer(Url);
_server.RegisterModule(new LocalSessionModule());
_server.RegisterModule(new StaticFilesModule(_filePath));
_server.Module<StaticFilesModule>().UseRamCache = true;
_server.Module<StaticFilesModule>().DefaultExtension = ".html";
_server.Module<StaticFilesModule>().DefaultDocument = "index.html";
_server.Module<StaticFilesModule>().UseGzip = false;
Task.Factory.StartNew(async ()=>
{
Debug.WriteLine("Starting Server");
await _server.RunAsync();
});
}
public void Dispose()
{
_server?.Dispose();
}
}
Code which starts the server and displays the webview:
public App()
{
InitializeComponent();
//Create and display a Webview
_webView = new WebView();
MainPage = new ContentPage()
{
Content = _webView,
};
}
protected override async void OnStart()
{
//Service which can initialize app for first time use, and stores
//the folder location for the html page on each platform
var htmlService = DependencyService.Get<IHandleHtmlContentService>();
//Local webserver
var localWebServer = new LocalWebServer(htmlService.DirectoryPath);
//This is just a function that loads the html content from the
//bundle resource or assets into a folder. Will only really
//matter during the first time the App boots up.
await htmlService.InitializeHtmlContent();
//Start the Webserver
localWebServer.StartWebServer();
//Navigate to the webserver
_webView.Source = LocalWebServer.Url;
}
I'v been bashing my head on this for a while, so any help would be appreciated. If you need any more details, let me know.
Turns out, Android has no concept of "localhost" (at least from what I can read). Instead, I need to find the IP Address of my device. I have done this with the following code:
public class LocalWebServer: IDisposable
{
public readonly string Url;
...
public LocalWebServer(string filePath)
{
_filePath = filePath;
Url = "http://" + GetLocalIpAddress() + ":8787/";
}
...
private static string GetLocalIpAddress()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
try
{
listener.Start();
return ((IPEndPoint)listener.LocalEndpoint).Address.ToString();
}
finally
{
listener.Stop();
}
}
}
Code was found on this Xamarin Forums post: https://forums.xamarin.com/discussion/42345/simple-android-http-listener-not-working

How to View the downloaded file When another download is in progress using Xamarin Android

I am implementing a mobile app using Xamarin android. I have implemented a code to download both.PDF and .Mobi file in a button click. I have used the below code.
...
await Task.WhenAll(DownloadPDF(), DownloadMobi());
}
private async Task DownloadPDF()
{
var httpclient = new HttpClient(new AndroidClientHandler());
using (var stream = await httpclient.GetStreamAsync("http://files/file.pdf"))
using (var file = System.IO.File.Create("path/to/file.pdf"))
{
await stream.CopyToAsync(file);
await file.FlushAsync();
}
}
private async Task DownloadMobi()
{
var httpclient = new HttpClient(new AndroidClientHandler());
using (var stream = await httpclient.GetStreamAsync("http://files/file.mobi"))
using (var file = System.IO.File.Create("path/to/file.mobi"))
{
await stream.CopyToAsync(file);
await file.FlushAsync();
}
}
Its download both file at a same time. I want to download the PDF file at first. Once PDF file has been downloaded the button text should be changed to "View PDF" from "Download". When click View PDF the the file should be opened in PDF reader. The Mobile file download should start after this process and download should be in background.
Can you anyone suggest your ideas to achieve this?
Since you're not showing any UI stuff i guess you have that covered to i will omit that.
Instead of writing: await Task.WhenAll(DownloadPDF(), DownloadMobi());
do the following
await DownloadPDF();
// update button to display "View PDF"
// add button click listener (optional if it's already registered)
// open file in PDF reader
await DownloadMobi();
Use ContinueWith Method of Task
var task = DownloadPDF();
task.ContinueWith((pdfDownloadTask)=> DownloadMobi());
This will continue execution of next task after pdf download task is complete
private stringBuilder urlStr = null;
public void DownloadFiles()
{
List<string> url = new List<string>();
urlStr = new StringBuilder();
url.add("http://files/file.pdf");
url.add("http://files/file.mobi");
var tasks = new List<Task>();
foreach(var tempUrl in url)
{
tasks.add(DownloadMobiAndPdf(tempUrl);
}
Task.WhenAll(tasks));
}
private async Task DownloadMobiAndPdf(string url)
{
using(var client = new WebClient())
{
urlStr.Append(url);
await client.DonwloadFileTaskAsync(url);
client.DownloadFileCompleted+=Client_DownloadFileCompleted;
}
}
private static void Client_DownloadFileCompleted(object
sender,System.ComponentModel.AsyncCompletedEventArgs e)
{
if(e.Error == null)
{
//No error
if(urlStr.Contains("pdf")
{
//Enable button
}
}
}

Not able to update Picker.SelectedIndex

I have an odd issue I can't explain the reason for it - maybe someone here can shed some light on it
I have a ticket scanning app in Xamarin Forms currently testing it on android
the interface allows you to:
type an order number and click the check order Button
use the camera scanner to scan which automatically triggers check order
use the barcode scanner to scan which automatically triggers check order
after the check order validation, user has to select the number of tickets from a drop down list and press confrim entry button
what I'm trying to do, is if the seats available on that ticket is just 1 - then automatically trigger confirm entry button functionality
problem that I have is that - some of my logic depends on setting the drop down index in code - for some reason it doesn't update - as seen in the debugger shot here
and this is the second tme I've noticed this today, earlier it was a var I was trying to assign a string and it kept coming up as null - eventually I replaced that code
is this a bug in xamarin ?
code has been simplified:
async void OnCheckOrderButtonClicked(object sender, EventArgs e)
{
await ValidateOrderEntry();
}
private async void scanCameraButton_Clicked(object sender, EventArgs e)
{
messageLabel.Text = string.Empty;
var options = new ZXing.Mobile.MobileBarcodeScanningOptions();
options.PossibleFormats = new List<ZXing.BarcodeFormat>() {
ZXing.BarcodeFormat.QR_CODE,ZXing.BarcodeFormat.EAN_8, ZXing.BarcodeFormat.EAN_13
};
var scanPage = new ZXingScannerPage(options);
scanPage.OnScanResult += (result) =>
{
//stop scan
scanPage.IsScanning = false;
Device.BeginInvokeOnMainThread(async () =>
{
//pop the page and get the result
await Navigation.PopAsync();
orderNoEntry.Text = result.Text;
//automatically trigger update
await ValidateOrderEntry();
});
};
await Navigation.PushAsync(scanPage);
}
private async Task ValidateOrderEntry()
{
//...other code....
checkInPicker.Items.Clear();
if (availablTickets == 1)
{
checkInPickerStack.IsVisible = true;
checkInPicker.SelectedIndex = 0;
messageLabel.Text = "Ticket OK! - " + orderNoEntry.Text;
messageLabel.TextColor = Color.Green;
//select the only element
checkInPicker.SelectedIndex = 0;
await PostDoorEntry();
}
//...other code....
}
private async Task PostDoorEntry()
{
int entryCount = checkInPicker.SelectedIndex + 1;
//... more code...
//...post api code..
}
Maybe I'm overlooking something, but you clear all the items a few lines above the one you are pointing out. That means there are no items in your Picker and thus you can't set the SelectedIndex to anything other than -1, simply because there are no items.

Need an example of take a Picture with MonoDroid and MVVMCross

Can anyone point me to an example of take a Photo and store it using MVVMCross?
I have been searching but only have found this:
Monodroid Take a picture with Camera (Doesn't Implement MVVMCross)
Video Recording (It's Video and i can't make it work :S)
The Oficialy Recipe Example (It Works but does not implement MVVMCross)
Thanks!!!
Resolved! Thanks!
To Future References: (Using Master Branch)
Credits to Stuart, I just changed the code to work with my reality
using Cirrious.MvvmCross.ExtensionMethods;
using Cirrious.MvvmCross.Interfaces.Platform.Tasks;
using Cirrious.MvvmCross.Interfaces.ServiceProvider;
using SIGEP.DummyService;
using SIGEP.Mobile.Core.Interfaces;
namespace SIGEP.Mobile.Core.Models
{
public class PhotoService : IMvxServiceConsumer<IMvxPictureChooserTask>
{
private const int MaxPixelDimension = 1024;
private const int DefaultJpegQuality = 92;
public void GetNewPhoto()
{
this.GetService<IMvxPictureChooserTask>().TakePicture(
MaxPixelDimension,
DefaultJpegQuality,
HandlePhotoAvailable,
() => { /* cancel is ignored */ });
}
public event EventHandler<PhotoStreamEventArgs> PhotoStreamAvailable;
private void HandlePhotoAvailable(Stream pictureStream)
{
var handler = PhotoStreamAvailable;
if (handler != null)
{
handler(this, new PhotoStreamEventArgs() { PictureStream = pictureStream, OnSucessGettingPhotoFileName = OnSucessGettingPhotoFileName });
}
}
public static void TakePhoto(Action<string> successFileName, Action<Exception> error)
{
var service = new PhotoService();
service.OnSucessGettingPhotoFileName = successFileName;
service.OnError = error;
service.GetNewPhoto();
service.PhotoStreamAvailable += new EventHandler<PhotoStreamEventArgs>(service_PhotoStreamAvailable);
}
static void service_PhotoStreamAvailable(object sender, PhotoStreamEventArgs e)
{
//grava pra ficheiro!!!
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(directory, "photo.jpeg");
string saveTo = filename;
FileStream writeStream = new FileStream(saveTo, FileMode.Create, FileAccess.Write);
ReadWriteStream(e.PictureStream, writeStream);
e.OnSucessGettingPhotoFileName(filename);
}
private static void ReadWriteStream(Stream readStream, Stream writeStream)
{
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
public Action<string> OnSucessGettingPhotoFileName { get; set; }
public Action<Exception> OnError { get; set; }
}
[Serializable]
[ComVisible(true)]
public class PhotoStreamEventArgs : EventArgs
{
public Stream PictureStream { get; set; }
public Action<string> OnSucessGettingPhotoFileName { get; set; }
}
}
I generally implement a service using the built-in IMvxPictureChooserTask (this is in a Plugin if using vNext):
using Cirrious.MvvmCross.ExtensionMethods;
using Cirrious.MvvmCross.Interfaces.Platform.Tasks;
using Cirrious.MvvmCross.Interfaces.ServiceProvider;
public class PhotoService
: IMvxServiceConsumer<IMvxPictureChooserTask>
, IPhotoService
{
private const int MaxPixelDimension = 1024;
private const int DefaultJpegQuality = 92;
public void GetNewPhoto()
{
Trace.Info("Get a new photo started.");
this.GetService<IMvxPictureChooserTask>().TakePicture(
MaxPixelDimension,
DefaultJpegQuality,
HandlePhotoAvailable,
() => { /* cancel is ignored */ });
}
public event EventHandler<PhotoStreamEventArgs> PhotoStreamAvailable;
private void HandlePhotoAvailable(Stream pictureStream)
{
Trace.Info("Picture available");
var handler = PhotoStreamAvailable;
if (handler != null)
{
handler(this, new PhotoStreamEventArgs() { PictureStream = pictureStream });
}
}
}
I generally register this service as a singleton during startup, and then call it from a ViewModel ICommand handler.
One app which uses this service is the Blooor sample - see BaseEditProductViewModel.cs - this isn't a sample I had anything to do with, but I believe it brings in both Picture taking and ZXing - both using external services.
One warning: On MonoDroid, you can see some strange/unexpected Activity/ViewModel lifecycle behaviour - basically you can see that the Activity you take the photo from is unloaded/wiped from memory during the photo taking. If this happens to your app then you'll probably need to start looking at questions like: Saving Android Activity state using Save Instance State - this isn't automatically handled in MvvmCross (yet).
I believe the Blooor sample might suffer from this issue - but whether a user would ever see it in normal app use is debatable.
As an alternative to the IMvxPictureChooserTask service, you can also look at using some of the cross-platform APIs from Xamarin.Mobile - see MvvmCross vnext : monodroid use a VideoView inside a plugin for a possible starting place - or for Android only you can easily implement your own.

Categories

Resources