Activity is null when try to take screen shot - c#

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;

Related

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);
}
}

How to Implement an iOS Overlay in Shared Code Using the Media Plugin for Xamarin.Forms?

I am trying to implement the overlay option for the media plugin in Xamarin, found here: https://github.com/jamesmontemagno/MediaPlugin
I understand how to use the feature in the iOS code, but am having trouble implementing it in the shared code.
Would really appreciate it if anyone knows what is wrong with my code, or if there is a working example of using the overlay feature in shared code.
I have setup an interface to handle the overlay for the iOS code, and have passed the function via a dependency service. It seems that the code block inside of func is skipping over when I set breakpoints and step into the function. The camera works but the overlay does not appear when using the camera.
IPhotoOverlay:
namespace Camera
{
public interface IPhotoOverlay
{
object GetImageOverlay();
}
}
AppDelegate:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
Xamarin.Forms.DependencyService.Register<IPhotoOverlay, PhotoOverlay_iOS>();
return base.FinishedLaunching(app, options);
}
PhotoOverlay_iOS:
[assembly: Xamarin.Forms.Dependency (typeof (PhotoOverlay_iOS))]
namespace Camera.iOS
{
public class PhotoOverlay_iOS: IPhotoOverlay
{
public PhotoOverlay_iOS ()
{
}
public object GetImageOverlay()
{
Func<object> func = () =>
{
var imageView = new UIImageView(UIImage.FromBundle("face-template.png"));
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
var screen = UIScreen.MainScreen.Bounds;
imageView.Frame = screen;
return imageView;
};
//Func<object> func = CreateOverlay;
return func;
}
}
}
Shared Code:
var photo = await Plugin.Media.CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions()
{
OverlayViewProvider = DependencyService.Get<IPhotoOverlay>().GetImageOverlay,
DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Front
});
Solution:
You can refer the following code .
in Forms
async void OpenCameraAsync()
{
Func<object> func = () =>
{
var obj = DependencyService.Get<IPhotoOverlay>().GetImageOverlay();
return obj;
};
var photo = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions()
{
OverlayViewProvider =func,
DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Front,
});
}
in iOS project
public object GetImageOverlay()
{
var imageView = new UIImageView(UIImage.FromBundle("yourimagename.png"));
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
var screen = UIScreen.MainScreen.Bounds;
imageView.Frame = screen;
return imageView;
}

Touch Id with Xamarin Forms Application

We are trying to use Touch Id with iOS using our Xamarin Forms application.
In our Xamarin Forms Application, in the App.Xaml.cs constructor we are using an interface to reference the iOS native touch id implementation:
bool _authenticatedWithTouchID = DependencyService.Get<ITouchID>().AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
This is the interface signature within Forms Application:
public interface ITouchID
{
bool AuthenticateUserIDWithTouchID();
}
This is the implementation of the interface within the iOS project:
[assembly: Dependency(typeof(TouchID))]
namespace GetIn.iOS
{
public class TouchID : ITouchID
{
public bool AuthenticateUserIDWithTouchID()
{
bool outcome = false;
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
Device.BeginInvokeOnMainThread(() => {
if (success)
{
outcome = true;
}
else
{
outcome = false;
}
});
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return outcome;
}
}
}
We get a response from the outcome variable (which is true if user is authenticated) but that is not being passed back to the forms application.
We have also tried using async tasks with no luck.
Is there a recommended way we do this? We are trying to keep this as simple as possible.
You need to change your code to handle asynchronous behavior.
public class TouchID : ITouchID
{
public Task<bool> AuthenticateUserIDWithTouchID()
{
var taskSource = new TaskCompletionSource<bool>();
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
taskSource.SetResult(success);
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return taskSource.Task;
}
}
Remember add the using on top
using System.Threading.Tasks;
And change your interface declaration
public interface ITouchID
{
Task<bool> AuthenticateUserIDWithTouchID();
}
And finally your Xamarin.Forms code...
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
Managed to get this working by using the async changes above (although you can do this without using the async method), and then doing the following:
By moving and adapting the following code from the app.xaml.cs to our MainPage.xaml.cs (our tabbed page) constructor.
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await
touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
//Do Nothing as on this page
}
else
{
//Go back to login page
Navigation.InsertPageBefore(new LoginPage(), this);
await Navigation.PopAsync();
}

Multithreading: Only the original thread that created a view hierarchy can touch its views

I am developing an app in c# where the class is supposed to download and update the userpicture and his landscape picture in an async task:
public PVH_Profile_Header(View itemView, Action<int> listener, Context ctx) : base(itemView)
{
this.ctx = ctx;
LoadContentAsync(ctx);
}
private async void LoadContentAsync(Context ctx)
{
ProgressDialog mDialog = new ProgressDialog(ctx);
mDialog = new ProgressDialog(ctx);
mDialog.SetMessage(ctx.Resources.GetString(Resource.String.Loading));
mDialog.SetCancelable(false);
mDialog.Show();
await InitAvatar(ctx);
await InitBigPicture(ctx);
mDialog.Dismiss();
}
but this thows the error from the title.
I tried doing:
Activity.RunOnUiThread(async () =>
{
await InitBigPicture(ctx);
});
But this gave me the error (translated:)
"For the non-static field, the method needs an reference to an object."
If you guys could help me out a little, that be awesome.
Addition info:
This class is a helper class to a recyclerview that is already loading its content async. So I got that to work just fine, but the recyclerview was inside an activity which made the wrapping in "RunOnUiThread" easier.
Thanks!
EDIT:
This is the method "initbigpic:"
private async Task InitBigPicture(Context ctx)
{
Bitmap pictures = null;
await Task.Run(() =>
{
if (string.Equals(strUsername, Account.Instance.Username)) // if own profile
{
pictures = SaveAccountInfo.LoadLandscapePicFull(0);
}
else
{
pictures = Intermediate.getLandscapePicture(strUsername, false, 0);
}
Drawable intHeitOfDrawable = ctx.Resources.GetDrawableForDensity
(Resource.Drawable.thebook_dummyBackgroundBigPicture,
(int)ctx.Resources.DisplayMetrics.DensityDpi);
pictures = Bitmap.CreateScaledBitmap(pictures, ctx.Resources.DisplayMetrics.WidthPixels,
intHeitOfDrawable.IntrinsicHeight, false);
btnBigPicture.SetImageBitmap(pictures);
});
}

Xamarin Forms Webview local

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/

Categories