Xamarin Forms Webview local - c#

I am trying to load a Web application (Readium) in a webview with Xamarin locally. As a target I have UWP, Android and iOS.
I can not get the index.html page open, I have embedded the web in each of the projects, according to https://developer.xamarin.com/guides/xamarin-forms/user-interface/webview/ but I get a blank page.
have implemented the dependency service for each application such as (UWP)
assembly: Dependency(typeof(BaseUrl))]
namespace WorkingWithWebview.UWP
{
public class BaseUrl : IBaseUrl
{
public string Get()
{
return "ms-appx-web:///";
}
}
}
However, creating a new UWP project (without Xamarin), it works well, using the method NavigateToLocalStreamUri(uri, new StreamUriWinRTResolver()) with
public IAsyncOperation<IInputStream> UriToStreamAsync(Uri uri)
{
if (uri == null)
{
throw new Exception();
}
string path = uri.AbsolutePath; 
return GetContent(path).AsAsyncOperation();
}
private async Task<IInputStream> GetContent(string path)
{  
try
{
Uri localUri = new Uri("ms-appx:///cloud-reader" + path);
StorageFile f = await StorageFile.GetFileFromApplicationUriAsync(localUri);
IRandomAccessStream stream = await f.OpenAsync(FileAccessMode.Read);
return stream;
}
catch (Exception)
{
throw new Exception("Invalid path");
}
}
In what way would the same be done in Xamarin Forms?
Thanks you.

Finally I achieve to load local content adding a custom render for each platform.
Example (UWP):
[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
namespace DisplayEpub.UWP{
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var customWebView = Element as WebView;
Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html"));
}
}
}
}
I've followed the example of Xamarin docs to display PDF using custom render, targeting 3 platforms. I've tested it on Android and Windows:
https://developer.xamarin.com/recipes/cross-platform/xamarin-forms/controls/display-pdf/

Related

Attempt to present QLPreviewController whose view is not in the window hierarchy issue

I am trying to open a downloaded file in Xamarin Forms for IOS platform using UIDocumentInteractionController and access platform specific class through webservice but it is giving me the issue as 'Attempt to present QLPreviewController whose view is not in the window hierarchy'
public void viewFile(string path)
{
var PreviewController = UIDocumentInteractionController.FromUrl
(NSUrl.FromFilename(path));
PreviewController.Delegate = new UIDocumentInteractionControllerDelegateClass
(UIApplication.SharedApplication.KeyWindow.RootViewController.PresentedViewController);
Device.BeginInvokeOnMainThread(() =>
{
PreviewController.PresentPreview(true);
});
}
Following is the UIDocumentInteractionControllerDelegateClass code:
public class UIDocumentInteractionControllerDelegateClass : UIDocumentInteractionControllerDelegate {
UIViewController ownerVC;
public UIDocumentInteractionControllerDelegateClass(UIViewController vc)
{
ownerVC = vc;
}
public override UIViewController ViewControllerForPreview(UIDocumentInteractionController controller)
{
return ownerVC;
}
public override UIView ViewForPreview(UIDocumentInteractionController controller)
{
return ownerVC.View;
}
}
Try this code
var fileinfo = new FileInfo(path);
var previewController = new QLPreviewController();
previewController.DataSource = new PreviewControllerDataSource(fileinfo.FullName, fileinfo.Name);
UINavigationController controller = FindNavigationController();
if (controller != null)
{
controller.PresentViewController((UIViewController)previewController, true, (Action)null);
}

Xamarin.Forms: How to download an Image, save it locally and display it on screen?

I have an issue with opening JPG file in native application on Android.
I'm using newest release of Xamarin Essentials, there is some functionality called Launcher.
Here is my code
await Launcher.TryOpenAsync("file:///" + localPath);
My local path is file stored in Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);.
Whenever I try to open that file I am getting error:
file:////data/user/0/mypackagename/files/.local/share/Screenshot.jpg exposed beyond app through Intent.getData()
I found several solutions here on stackoverflow, but I don't want to use Intent because my application is designed to be cross-platform (I would like to avoid native platform coding if this possible).
Launcher throws error on iOS also:
canOpenURL: failed for URL: ** -- file:///" - error: "This app is not allowed to query for scheme file
What am I doing wrong here?
Step 1: Download the Image
We can use HttpClient to download the image.
HttpClient.GetByteArrayAsync will retrieve the image data and save it in memory.
In DownloadImage below, we will retrieve the image as a byte[].
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
}
Step 2 Save Image to Disk
Now that we've downloaded the image, we will save it to disk.
Xamarin.Essentials.Preferences allows us to save items to disk using key-value pairs. Since byte[] is just a pointer to memory, we'll have to first convert the byte[] to base64-string before we can save it to disk.
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
public static void SaveToDisk(string imageFileName, byte[] imageAsBase64String)
{
Xamarin.Essentials.Preferences.Set(imageFileName, Convert.ToBase64String(imageAsBase64String));
}
}
Step 3 Retrieve the Image for Display
Now that we've downloaded the image and saved it to disk, we need to be able to retrieve the image from disk to display it on the screen.
GetFromDisk below retrieves the image from disk and converts it to Xamarin.Forms.ImageSource.
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
public static void SaveToDisk(string imageFileName, byte[] imageAsBase64String)
{
Xamarin.Essentials.Preferences.Set(imageFileName, Convert.ToBase64String(imageAsBase64String));
}
public static Xamarin.Forms.ImageSource GetFromDisk(string imageFileName)
{
var imageAsBase64String = Xamarin.Essentials.Preferences.Get(imageFileName, string.Empty);
return ImageSource.FromStream(() => new MemoryStream(Convert.FromBase64String(imageAsBase64String)));
}
}
Example: Using ImageService in a Xamarin.Forms.ContentPage
class App : Application
{
public App() => MainPage = new MyPage();
}
class MyPage : ContentPage
{
readonly Image _downloadedImage = new Image();
public MyPage()
{
Content = _downloadedImage;
}
protected override async void OnAppearing()
{
const string xamrainImageUrl = "https://cdn.dribbble.com/users/3701/screenshots/5557667/xamarin-studio-1_2x_4x.png"
const string xamarinImageName = "XamarinLogo.png";
var downloadedImage = await ImageService.DownloadImage(xamrainImageUrl);
ImageService.SaveToDisk(xamarinImageName, downloadedImage);
_downloadedImage.Source = ImageService.GetFromDisk(xamarinImageName);
}
}

Activity is null when try to take screen shot

My project type is PCL in xamarin Forms. I want to take screenshot and I am using below code. But when I debug my code Activity is always null. I am trying to figure out the issue but not get success. Can you please tell what i am doing the mistake.
public interface IScreenshotManager
{
Task<byte[]> CaptureAsync();
}
For Android:
[assembly: Xamarin.Forms.Dependency(typeof(ScreenshotService))]
namespace MyProject.Droid
{
public class ScreenshotService : IScreenshotService
{
public static Activity Activity { get; set; }
public async System.Threading.Tasks.Task<byte[]> Capture()
{
if (Activity == null) // Activity is always null Error here
{
throw new Exception("You have to set ScreenshotManager.Activity in your Android project");
}
var view = Activity.Window.DecorView;
view.DrawingCacheEnabled = true;
Bitmap bitmap = view.GetDrawingCache(true);
byte[] bitmapData;
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}
}
}
Calling the function
byte[] screenshotData = await DependencyService.Get<IScreenshotService>().Capture();
Obviously Activity is null if you don't assign it. You can use the CurrentActivity nuget package to get the current activity (or you may assign it in Activity OnCreate method as Xamarin uses always the same Activity from the start).
The old deprecated way would be var view = ((Activity)Xamarin.Forms.Forms.Context).Window.DecorView;
Xamarin automatically assigns the Activity to Forms.Context.Since the release of Xamarin 2.5, Xamarin.Forms.Forms.Context is obsolete.
Now you could use this :
var currentContext = Android.App.Application.Context;

Opening a PDF with Xamarin Forms

I have a pdf i've added as an AndroidAsset and a BundleResource for my Android and IOS projects using xamarin forms.
I just want to be able to open those files from any device, using whatever pdf viewer the device defaults to.
Essentially, i just want to be able to do something like:
Device.OpenUri("file:///android_asset/filename.pdf");
but this doesn't seem to work. Nothing happens and the user is never prompted to open the pdf. I don't want to use any 3rd party libraries that allow the pdf to open in app, i just want it to redirect the user to a pdf viewer or browser.
Any ideas?
First of all you will need an interface class, since you need to call the dependency service in order to pass your document to the native implementation(s) of your app:
so in your shared code add an interface, called "IDocumentView.cs":
public interface IDocumentView
{
void DocumentView(string file, string title);
}
Android
Now in your android project create the corresponding implementation "DocumentView.cs":
assembly: Dependency(typeof(DocumentView))]
namespace MyApp.Droid.Services
{
public class DocumentView: IDocumentView
{
void IDocumentView.DocumentView(string filepath, string title)
{
try
{
File file = new File(filepath);
String mime = FileTypes.GetMimeTypeByExtension(MimeTypeMap.GetFileExtensionFromUrl(filepath));
File extFile = new File (Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments), file.Name);
File extDir = extFile.ParentFile;
// Copy file to external storage to allow other apps to access ist
if (System.IO.File.Exists(extFile.AbsolutePath))
System.IO.File.Delete(extFile.AbsolutePath);
System.IO.File.Copy(file.AbsolutePath, extFile.AbsolutePath);
file.AbsolutePath, extFile.AbsolutePath);
// if copying was successful, start Intent for opening this file
if (System.IO.File.Exists(extFile.AbsolutePath))
{
Intent intent = new Intent();
intent.SetAction(Android.Content.Intent.ActionView);
intent.SetDataAndType(Android.Net.Uri.FromFile(extFile), mime);
MainApplication.FormsContext.StartActivityForResult(intent, 10);
}
}
catch (ActivityNotFoundException anfe)
{
// android could not find a suitable app for this file
var alert = new AlertDialog.Builder(MainApplication.FormsContext);
alert.SetTitle("Error");
alert.SetMessage("No suitable app found to open this file");
alert.SetCancelable(false);
alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
alert.Show();
}
catch (Exception ex)
{
// another exception
var alert = new AlertDialog.Builder(MainApplication.FormsContext);
alert.SetTitle("Error");
alert.SetMessage("Error when opening document");
alert.SetCancelable(false);
alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
alert.Show();
}
}
}
}
Please note that MainApplication.FormsContext is a static variable I added to my MainApplication.cs in order to be able to access the Context of the app quickly.
In your Android manifest, add
In your application resources, add an xml resource (into folder "xml") called file_paths.xml with the following content:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="root" path="/"/>
<external-files-path name="files" path="files" />
</paths>
Also you need to ensure that there are apps installed on the target device, which are able to handle the file in question. (Acrobat Reader, Word, Excel, etc...).
iOS
iOS already comes with a quite nice document preview built in, so you can simply use that (again create a file named "DocumentView.cs" in your iOS Project):
[assembly: Dependency(typeof(DocumentView))]
namespace MyApp.iOS.Services
{
public class DocumentView: IDocumentView
{
void IDocumentView.DocumentView(string file, string title)
{
UIApplication.SharedApplication.InvokeOnMainThread(() =>
{
QLPreviewController previewController = new QLPreviewController();
if (File.Exists(file))
{
previewController.DataSource = new PDFPreviewControllerDataSource(NSUrl.FromFilename(file), title);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null);
}
});
}
}
public class PDFItem : QLPreviewItem
{
public PDFItem(string title, NSUrl uri)
{
this.Title = title;
this.Url = uri;
}
public string Title { get; set; }
public NSUrl Url { get; set; }
public override NSUrl ItemUrl { get { return Url; } }
public override string ItemTitle { get { return Title; } }
}
public class PDFPreviewControllerDataSource : QLPreviewControllerDataSource
{
PDFItem[] sources;
public PDFPreviewControllerDataSource(NSUrl url, string filename)
{
sources = new PDFItem[1];
sources[0] = new PDFItem(filename, url);
}
public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
int idx = int.Parse(index.ToString());
if (idx < sources.Length)
return sources.ElementAt(idx);
return null;
}
public override nint PreviewItemCount(QLPreviewController controller)
{
return (nint)sources.Length;
}
}
}
Finally you can call
DependencyService.Get<IDocumentView>().DocumentView(file.path, "Title of the view");
to display the file in question.

How to pass device token to PCL from Android MainActivity?

I have added code in MainActivity.cs file for generating Device Id. Now I want to pass that device token to my PCL project main page, How's that possible? I also want to know about how to generate Device Token in IOS app? and how to pass that token to Portable Class Library?
Code Sample :
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.GetString(key);
Log.Debug("Key: {0} Value: {1}", key, value);
}
}
FirebaseApp.InitializeApp(this);
var instanceId = FirebaseInstanceId.Instance;
if (FirebaseInstanceId.Instance.Token != null)
Log.Debug("MyToken", FirebaseInstanceId.Instance.Token.ToString());
}
}
I need this "My Token" data on login page button Click event. Hows this possible?
My Login Page Code is
public partial class LoginPage : ContentPage
{
private readonly DataService _dataService = new DataService();
public LoginPage()
{
InitializeComponent();
}
private async Task BtnLogin_ClickedAsync(object sender, EventArgs e)
{
var result = await _dataService.Authentication(TxtUserName.Text, TxtPassword.Text,"MyToken");
if (result.AccessToken != null)
{
await Navigation.PushModalAsync(new MainMasterPage());
GlobalClass.userToken = result;
}
else
await DisplayAlert("", Resource.InvalidMessage, Resource.OkText);
}
}
Welcome to the Realm of Dependency Injection :)
documentation can be found here
You need to create a interface on your PCL then reference that on your Native project
Example:
Create class DeviceToke.cs in your PCL
public interface ITextToSpeech
{
void Speak(string text);
}
Then in your native project, you can do the following:
sample code:
[assembly: Dependency(typeof(TextToSpeechAndroidImpl))]
namespace IocAndDiXamarinForms.Droid
{
public class TextToSpeechAndroidImpl : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener
{
TextToSpeech speaker;
string toSpeak;
public void Speak(string text)
{
var ctx = Forms.Context; // useful for many Android SDK features
toSpeak = text;
if (speaker == null)
{
speaker = new TextToSpeech(ctx, this);
}
else
{
var p = new Dictionary<string, string>();
speaker.Speak(toSpeak, QueueMode.Flush, p);
}
}
#region IOnInitListener implementation
public void OnInit(OperationResult status)
{
if (status.Equals(OperationResult.Success))
{
var p = new Dictionary<string, string>();
speaker.Speak(toSpeak, QueueMode.Flush, p);
}
}
#endregion
}
}
You can use the Xamarin messaging center to pass a message back from your platform-specific classes to your PCL ViewModel. You'll need to subscribe to the message in your VM, and send the message from your Android or iOS class. Then you can store the value in your VM and use it when the user clicks login.
Sending the message:
Xamarin.Forms.MessagingCenter.Send(FirebaseInstanceId.Instance.Token.ToString(), "InstanceId");
Subscribing in your VM:
MessagingCenter.Subscribe<string> (this, "InstanceId", (InstanceId) => {
// use the InstanceId as required
});
});
A handy solution is to define a publicly accessible static StrToken property in some public class, e.g. App:
public static Size Token;
and OnCreate on Android:
App.StrToken = FirebaseInstanceId.Instance;

Categories