Xamarin forms: NoClassDefFoundError Landroidx/appcompat/app/AppCompatActivity - xamarin.forms

I'm trying to use a native android library called "ImageCropper" available here: https://github.com/ArthurHub/Android-Image-Cropper
I created android bindings, but when I try to consume it from XF custom rendered, it throws an error saying: "java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/app/AppCompatActivity"
I'm compiling my XF app with target version set to API 29. Not sure what I'm missing or how would I proceed to fix this issue. Any suggestions are heartly appreciated. Thanks.
PS:
XF custom renderer: https://pastebin.com/85Mdsy8c
public class ImagePickerService : IImagePickerService
{
public IImageSourceUtility ImageSourceUtility => new ImageSourceUtility();
private void StartActivity()
{
var currentActivity = MainActivity.AppActivity;
if (currentActivity != null)
{
var cropImageOptions = new CropImageOptions();
cropImageOptions.MultiTouchEnabled = true;
cropImageOptions.Guidelines = CropImageView.Guidelines.On;
cropImageOptions.AspectRatioX = 1;
cropImageOptions.AspectRatioY = 1;
cropImageOptions.FixAspectRatio = true;
cropImageOptions.Validate();
var intent = new Intent();
intent.SetClass(currentActivity, Class.FromType(typeof(ImagePickerOnResultActivity)));
intent.PutExtra(CropImage.CropImageExtraSource, null as global::Android.Net.Uri); // Image Uri
intent.PutExtra(CropImage.CropImageExtraOptions, cropImageOptions);
currentActivity.StartActivity(intent);
}
else
{
throw new InvalidOperationException("Could not get current activity.");
}
}
public Task<ImageSource> PickImageAsync()
{
StartActivity();
return Task.Run(() =>
{
_waitHandle.WaitOne();
var result = _pickAsyncResult;
_pickAsyncResult = null;
return result;
});
}
private static ImageSource _pickAsyncResult;
private static EventWaitHandle _waitHandle = new AutoResetEvent(false);
// if crashes(in release mode after linking), see plugin desc for proguard config change required for this theme
[Activity(Label = "CropImageActivity", Theme = "#style/Base.Theme.AppCompat")]
//[Activity(Theme = "#style/Base.Theme.AppCompat")]
public class ImagePickerOnResultActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
//base.OnCreate(savedInstanceState);
// start activity
var x = CropImage.Activity();
x.SetGuidelines(CropImageView.Guidelines.On)
.Start(this);
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
if (requestCode == CropImage.CropImageActivityRequestCode)
{
CropImage.ActivityResult result = CropImage.GetActivityResult(data);
if (resultCode == Result.Ok)
{
var croppedFileUri = new Uri(result.Uri.ToString());
_pickAsyncResult = ImageSource.FromFile(croppedFileUri.LocalPath);
_waitHandle.Set();
}
else if ((int)resultCode == CropImage.CropImageActivityResultErrorCode)
{
Java.Lang.Exception error = result.Error;
}
}
}
}
}
org GH repo (but bindings used here is outdated): https://github.com/matheusneder/Xamarin.Android.ImageCropper/tree/master/Source/Xamarin.Android.ImageCropper.App

Okay, so even with the target version set to API level 29, androidX packages were not being included for some reason. So, manually including AndroidX packages by adding the following package references in csproj file of the android project got rid of the above-mentioned error.
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.2.0.1" />
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.2.0.1" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0.1" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.1" />
This library required the following additional packages:
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0.1" />
<PackageReference Include="Xamarin.AndroidX.ExifInterface" Version="1.1.0.1" />
A better approach would be including these dependencies inside the binding library itself, but I could not figure out how to do it.
Anyway, this solution works for now, and I'm able to compile my XF project and use features from this library as expected.

Related

Which package should I use to embed admob in xamarin-forms?

I was thinking to use the Xamarin.GooglePlayServices.Ads.Lite package https://www.youtube.com/watch?v=6teJvSCg6UA&t=661s
But I can't find up-to-date instruction for embedding admob in xamarin-forms.
I registered with admob but found instructions only for kotlin and java.
Which package is currently relevant for xamarin-forms? And is there an up-to-date implementation guide?
You could create a control and do that with custom renderer.
Custom control:
public class AdControlView : View
{
}
Custom renderer:
[assembly: ExportRenderer(typeof(AdControlView), typeof(AdViewRenderer))]
namespace App14.Droid
{
public class AdViewRenderer : ViewRenderer<AdControlView, AdView>
{
public AdViewRenderer(Context context) : base(context)
{
}
string adUnitId = string.Empty;
//Note you may want to adjust this, see further down.
AdSize adSize = AdSize.Banner;
AdView adView;
AdView CreateNativeAdControl()
{
if (adView != null)
return adView;
adUnitId = "ca-app-pub-3940256099942544/6300978111"; //you could create you own unit id.
adView = new AdView(Forms.Context);
adView.AdSize = adSize;
adView.AdUnitId = adUnitId;
var adParams = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
adView.LayoutParameters = adParams;
adView.LoadAd(new AdRequest
.Builder()
.Build());
return adView;
}
protected override void OnElementChanged(ElementChangedEventArgs<AdControlView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
CreateNativeAdControl();
SetNativeControl(adView);
}
}
}
}
Create your own application id and unitid or you could get the test ads on Google Admob.
For more details, please refer to the case i done before.
How to use App ID in Xamarin Visual Studio

Xamarin.Forms Directory Picker

I would like to save a file in a user selected folder, thats why I would like to provide a directory list to user and user will be able to choose the directory where he wants to export the data. Unfortuntely I could not find any example for directory/folder picker, I just found a file picker which is not useful for me..
https://github.com/jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows
Is there any component for picking a folder for Xamarin.Forms? Actually I am just doing for Android but we use Xamarin.forms
There is none I can think of.
With netstandard everything is way more simple as you can use the classic c# File api to get the folders.
You just have to know the mappings between special folders and android folders (per example):
System.Environment.SpecialFolder Path
ApplicationData INTERNAL_STORAGE/.config
Desktop INTERNAL_STORAGE/Desktop
LocalApplicationData INTERNAL_STORAGE/.local/share
MyDocuments INTERNAL_STORAGE
MyMusic INTERNAL_STORAGE/Music
MyPictures INTERNAL_STORAGE/Pictures
MyVideos INTERNAL_STORAGE/Videos
Personal INTERNAL_STORAGE
source: https://learn.microsoft.com/en-US/xamarin/android/platform/files/
same for ios:
https://learn.microsoft.com/en-US/xamarin/ios/app-fundamentals/file-system
But it's really easy to implement, just enumerate all folders and display them in a ListView.
EDIT: more details on implementation.
In fact you want to code a "directory explorer", it's easy, here is the concept.
You have a ListView in your Page
You have a Cancel button and a Select button in your Page
You have a CurrentPath in your ViewModel
You bind CurrentPath to the Title of your Page
You have an List<DirectoryViewModel> Directories in your ViewModel
Each time a user click on a item from the list:
You add the directory name in your current path
You get all the directories from the new path, and update your Directories property (don't forget RaisePropertyChange(nameof(Directories)))
The ListView will be updated accordingly
Each time you back:
You remove last part of your current path
same as before
If you arrive to root path "/", you do nothing when clicking on back.
Oh and you could use this Grid component to instead of the ListView, will be nicer ;)
https://github.com/roubachof/Sharpnado.Presentation.Forms#grid-Layout
You can edit this to make it work..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
using Java.Util;
namespace Android.Basic.IO
{
public class DirectoryPicker : ListActivity
{
public const String START_DIR = "startDir";
public const String ONLY_DIRS = "onlyDirs";
public const String SHOW_HIDDEN = "showHidden";
public const String CHOSEN_DIRECTORY = "chosenDir";
public const int PICK_DIRECTORY = 43522;
private File dir;
private Boolean showHidden = false;
private bool onlyDirs = true;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
Bundle extras = Intent.Extras;
dir = OS.Environment.ExternalStorageDirectory;
if (extras != null)
{
String preferredStartDir = extras.GetString(START_DIR);
showHidden = extras.GetBoolean(SHOW_HIDDEN, false);
onlyDirs = extras.GetBoolean(ONLY_DIRS, true);
if (preferredStartDir != null)
{
File startDir = new File(preferredStartDir);
if (startDir.IsDirectory)
{
dir = startDir;
}
}
}
SetContentView(Resource.Layout.folder_chooser_activity);
var title = dir.AbsolutePath.ToString();
Title = (title);
Button btnChoose = (Button)FindViewById(Resource.Id.btnChoose);
String name = dir.Name;
if (name.Length == 0)
name = "/";
btnChoose.Text = ("Choose " + "'" + name + "'");
btnChoose.Click += delegate
{
returnDir(dir.AbsolutePath);
};
ListView lv = this.ListView;
lv.TextFilterEnabled = (true);
if (!dir.CanRead())
{
Context context = ApplicationContext;
String msg = "Could not read folder contents.";
Toast.MakeText(context, msg, ToastLength.Long).Show();
return;
}
var files = filter(dir.ListFiles(), onlyDirs, showHidden);
String[] names = Names(files);
ListAdapter = (new ArrayAdapter<String>(this, Resource.Layout.folder_chooser_item, names));
lv.ItemClick += (ff, gg) =>
{
var position = gg.Position;
if (!files[gg.Position].IsDirectory)
return;
String path = files[position].AbsolutePath;
var intent = new Intent(this, typeof(DirectoryPicker));
intent.PutExtra(DirectoryPicker.START_DIR, path);
intent.PutExtra(DirectoryPicker.SHOW_HIDDEN, showHidden);
intent.PutExtra(DirectoryPicker.ONLY_DIRS, onlyDirs);
StartActivityForResult(intent, PICK_DIRECTORY);
};
}
protected void OnActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == PICK_DIRECTORY && resultCode == (int)Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
returnDir(path);
}
}
private void returnDir(String path)
{
Intent result = new Intent();
result.PutExtra(CHOSEN_DIRECTORY, path);
SetResult(Result.Ok, result);
Finish();
}
public List<File> filter(File[] file_list, bool onlyDirs, bool showHidden)
{
var files = new List<File>();
foreach (var file in file_list)
{
if (onlyDirs && !file.IsDirectory)
continue;
if (!showHidden && file.IsHidden)
continue;
files.Add(file);
}
Collections.Sort(files);
return files;
}
public String[] Names(List<File> files)
{
String[] names = new String[files.Count];
int i = 0;
foreach (var file in files)
{
names[i] = file.Name;
i++;
}
return names;
}
}
}
Start activity as result then catch in OnActivityResult
if (requestCode == DirectoryPicker.PICK_DIRECTORY && resultCode == Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
// do stuff with path
}

Xamarin.Forms and Plugin.Media: after about 20 photos something crashes

I have a problem with Xamarin.Forms ver. 2.3.4.224 and Plugin.Media ver. 2.6.2. The problem occurs after taking about 20 photos (depends from the device): basically the app crashes without any apparently reason.
If you want to replicate the error, I created a test project for you on GitHub. With my iPad Air or iPad Pro after about 30 photos (video iPad Air - iPad Pro). All devices are iOS ver. 10.3.1 and they have enough space to storage photos.
The app is very simple: you have two buttons one for taking a picture and the other one to pick a photo. If you take photos one after another, after about 20 (32 in an iPad Air) the app crashes. I'm just take photos with the Plugin.Media nothing more.
Any ideas are welcome.
Update
In my project I had a reference to Refractored.MvvmHelpers and I noticed if I remove it, I can take more pictures. I created my BaseViewModel with INotifyPropertyChanged and I noticed I can take more photos.
I created then a new project (you can find it on GitHub under cameratesteasy) without MVVM and there is just the code to take a photo like:
public partial class cameratesteasyPage : ContentPage
{
int count = 0;
public cameratesteasyPage()
{
InitializeComponent();
CrossMedia.Current.Initialize();
}
void UpdateCount()
{
count++;
CountLabel.Text = $"{count} times";
}
async void StartCameraTapped(object sender, System.EventArgs args)
{
using (var file = await CrossMedia.Current.TakePhotoAsync(
new StoreCameraMediaOptions {}))
{
if (file == null)
return;
UpdateCount();
}
}
async void StartCameraTakeTapped(object sender, System.EventArgs args)
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
UpdateCount();
}
}
In this case the app shut down after 52 photos. I saved the log for Xcode and you can see it here.
I used Xamarin Profile and the memory level is always low. After about 30 photos, an error occurs in Xamarin Profiler
Finally I could create a Xamarin Profiler file
Also I noticed this kind of error occurs on iPads. The same app in an iPhone is working fine (apparently) or I didn't find up to now the number of photos before crashing.
Update /2
I decided to implement a native function for taking photo.
Interface
public interface ICamera
{
void TakePicture();
}
Implementation
using System;
using cameratest.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
[assembly: Xamarin.Forms.Dependency(typeof(Camera_iOS))]
namespace cameratest.iOS
{
public class Camera_iOS : ICamera
{
static UIImagePickerController picker;
static Action<NSDictionary> _callback;
static void Init()
{
if (picker != null)
return;
picker = new UIImagePickerController();
picker.Delegate = new CameraDelegate();
}
class CameraDelegate : UIImagePickerControllerDelegate
{
public override void FinishedPickingMedia(
UIImagePickerController picker, NSDictionary info)
{
var cb = _callback;
_callback = null;
picker.DismissModalViewController(true);
cb(info);
}
}
public static void TakePicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public static void SelectPicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public void TakePicture()
{
var rc = UIApplication.SharedApplication.KeyWindow.RootViewController;
TakePicture(rc, (obj) =>
{
var photo = obj.ValueForKey(
new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
var documentsDirectory =
Environment.GetFolderPath(Environment.SpecialFolder.Personal);
// hardcoded filename, overwritten each time
string jpgFilename = System.IO.Path.Combine(documentsDirectory,
"Photo.jpg");
NSData imgData = photo.AsJPEG();
NSError err = null;
if (imgData.Save(jpgFilename, false, out err))
{
Console.WriteLine("saved as " + jpgFilename);
}
else
{
Console.WriteLine("NOT saved as " +
jpgFilename + " because" + err.LocalizedDescription);
}
});
}
}
}
With this code after about 30 photos, the app crashes. The only difference is with this code I can receive some alert from ReceiveMemoryWarning. If you have an interest, I updated the code on GitHub.

BackgroundTaskRegistration trigger property is null

Hi want to receive push notifications on background task for that i have created Portable library here is my Background task class
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Windows.ApplicationModel.Background;
using Windows.Data.Xml.Dom;
using Windows.Networking.PushNotifications;
using Windows.Storage;
using Windows.UI.Notifications;
namespace BackgroundTask
{
public sealed class NotificationTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
// Get the background task details
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
string taskName = taskInstance.Task.Name;
Debug.WriteLine("Background " + taskName + " starting...");
// Store the content received from the notification so it can be retrieved from the UI.
ToastNotification notification = (ToastNotification )taskInstance.TriggerDetails;
settings.Values[taskName] = notification.Content;
NotificationTask.AddTostNotification(notification.Content);
Debug.WriteLine("Background " + taskName + " completed!");
}
private static void AddTostNotification(String xmlDocument)
{
List<string> messageSection = NotificationTask.GetMessageAndLandingPage(xmlDocument, "toast");
if (messageSection == null) { return; }
ToastTemplateType toastTemplate = ToastTemplateType.ToastText01;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode(messageSection[0]));
// toastTextElements[1].AppendChild(toastXml.CreateTextNode(message));
IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
((XmlElement)toastNode).SetAttribute("launch", messageSection[1]);
XmlElement audio = toastXml.CreateElement("audio");
audio.SetAttribute("src", "ms-appx:///Assets/Play-Guitar.wav");
//audio.SetAttribute("loop", "true");
toastNode.AppendChild(audio);
//launch tost immediatly
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
Here i am registering Task
internal async void InitChannel()
{
// Applications must have lock screen privileges in order to receive raw notifications
BackgroundAccessStatus backgroundStatus = await BackgroundExecutionManager.RequestAccessAsync();
// Make sure the user allowed privileges
if (backgroundStatus != BackgroundAccessStatus.Denied && backgroundStatus != BackgroundAccessStatus.Unspecified)
{
Windows.Storage.ApplicationDataContainer roamingSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
try
{
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
if (channel != null)
{
roamingSettings.Values["ExistingPushChannel"] = channel.Uri;
dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
channel.PushNotificationReceived += OnPushNotificationReceived;
UnregisterBackgroundTask();
RegisterBackgroundTask();
}
else
{
roamingSettings.Values["ExistingPushChannel"] = "Failed to create channel";
}
}
catch
{
roamingSettings.Values["ExistingPushChannel"] = "Failed to create channel";
}
}
}
private void RegisterBackgroundTask()
{
BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder();
PushNotificationTrigger trigger = new PushNotificationTrigger();
taskBuilder.SetTrigger(trigger);
// Background tasks must live in separate DLL, and be included in the package manifest
// Also, make sure that your main application project includes a reference to this DLL
taskBuilder.TaskEntryPoint = "BackgroundTask.NotificationTask";
taskBuilder.Name = "PlaypushNotification";
try
{
BackgroundTaskRegistration task = taskBuilder.Register();
task.Completed += BackgroundTaskCompleted;
}
catch
{
UnregisterBackgroundTask();
}
}
private bool UnregisterBackgroundTask()
{
foreach (var iter in BackgroundTaskRegistration.AllTasks)
{
IBackgroundTaskRegistration task = iter.Value;
if (task.Name == "PlaypushNotification")
{
task.Unregister(true);
return true;
}
}
return false;
}
In my Manifest file
<Extensions>
<Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTask.NotificationTask">
<BackgroundTasks>
<Task Type="pushNotification" />
</BackgroundTasks>
</Extension>
</Extensions>
PushNotification Trigger is not firing, when i debug i found that trigger property of BackgroundTaskRegistration is null. what is the issue? What wrong is going here?

How to programmatically sign a project in c sharp

I am creating projects from a template using DTE. I want the projects to be signed by a particular .snk file. How to do it programmatically??
please help..!!
Thanks,
Girish
What I did is add a new Addin project into my solution and by adding this code:
public void OnConnection(object application, ext_ConnectMode connectMode, objec addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
SetSign(_applicationObject);
}
public void SetSign(DTE2 app)
{
Solution solution = app.Solution;
foreach (Project proj in solution.Projects)
{
if (null != proj.Properties && null != proj.Properties.Item("SignAssembly"))
{
Property projProperty = proj.Properties.Item("SignAssembly");
bool signed = (bool)projProperty.Value;
if (!signed)
{
proj.Properties.Item("AssemblyOriginatorKeyFile").Value = #"C:\Projects\ClassLibrary1\Addins\Tools\mykeyfile.pfx";
proj.Properties.Item("SignAssembly").Value = true;
}
proj.Save();
}
}
}

Resources