android media picker from Xamarin forms - xamarin

I am writting an application with xamarin forms for iOS and Android.
I want to pick a photo from image gallery.
I have created an android specific static helper:
var i = new Intent();
i.SetType("*/*");
Forms.Context.StartActivity(Intent.CreateChooser(i, ""));
But i have no way to get the selected picture bytes.
I have seen on android tutorials i should implement onActivityResult, but i am not on an activity, this is a specific static call...
Thanks

Via a Form's dependency service:
Create your dependency interface (IMediaPicker)
Create a Activity subclass (MediaChooserActivityProxy) that will act as your Intent.ActionPick proxy
In your Xamarin.Android implementation of the IMediaPicker, use a AutoResetEvent to convert the Android StartActivityForResult / OnActivityResult callback to an await-able synchronous flow.
Dependency Service Interace:
public interface IMediaPicker
{
Task<string> ChooseAFileAsync();
}
Android Dependency Implementation:
public class MediaPicker : IMediaPicker
{
public static string filePickedPath;
public static AutoResetEvent waitHandle;
public async Task<string> ChooseAFileAsync()
{
waitHandle = new AutoResetEvent(false);
filePickedPath = "";
Forms.Context.StartActivity(typeof(MediaChooserActivityProxy));
await Task.Run(() => waitHandle.WaitOne());
return filePickedPath;
}
}
The Proxy/Pseudo Activity to capture OnActivityResult:
public class MediaChooserActivityProxy : Activity
{
const string mimeType = "image/*";
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
var intent = new Intent(Intent.ActionPick);
intent.SetType(mimeType);
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
intent.PutExtra(Intent.ExtraMimeTypes, mimeType);
}
StartActivityForResult(Intent.CreateChooser(intent, "StackOverflow"), 73);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == 73)
if (resultCode == Result.Ok)
{
string[] filePathColumn = { MediaStore.Images.ImageColumns.Data };
var cursor = ContentResolver.Query(data.Data, filePathColumn, null, null, null);
cursor.MoveToFirst();
var colummIndex = cursor.GetColumnIndex(filePathColumn[0]);
MediaPicker.filePickedPath = cursor.GetString(colummIndex);
}
MediaPicker.waitHandle.Set();
Finish();
}
}
Note: This can be implemented on the MainActivity/FormsAppCompatActivity to avoid this additional Activity if desired...
Usage:
var filePath = await DependencyService.Get<IMediaPicker>().ChooseAFileAsync();
System.Diagnostics.Debug.WriteLine(filePath);

Related

MAUI send sms without user interaction in Maui

I want to send a SMS in MAUI without opening the default messages App, I want to send the SMS silently in background. Does anyone know how to implement it?
Here is the implementation in MAUI.
Tested for Android and it works without opening the messages app. Here is the implementation for Android and iOS (not tested).
in the shared project create this class:
public partial class SmsService
{
public partial void Send(string address, string message);
}
Implementation for Android platform:
public partial class SmsService
{
public partial void Send(string phonenbr, string message)
{
SmsManager smsM = SmsManager.Default;
smsM.SendTextMessage(phonenbr, null, message, null, null);
}
}
Implementation for iOS platform (not tested):
public partial class SmsService
{
public partial void Send(string address, string message)
{
if (!MFMailComposeViewController.CanSendMail)
return;
MFMessageComposeViewController smsController = new MFMessageComposeViewController();
smsController.Recipients = new[] { address };
smsController.Body = message;
EventHandler<MFMessageComposeResultEventArgs> handler = null;
handler = (sender, args) =>
{
smsController.Finished -= handler;
var uiViewController = sender as UIViewController;
if (uiViewController == null)
{
throw new ArgumentException("sender");
}
uiViewController.DismissViewControllerAsync(true);
};
smsController.Finished += handler;
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(smsController, true);
}
}

How to use an interface

I'm trying to build my first xamarin app, which I'm building using forms. One of the features of the app is sending users locations and have to do that even if the app is in the background. So I came across James Montemagno's GeolocatorPlugin, which promised to do just that.
As the documentation was not that clear on how to implement his plugin in the background I looked through the projects closed issues and found a guy which gave an example of a simple case of using the plugin with a service. (https://github.com/jamesmontemagno/GeolocatorPlugin/issues/272)
I've adopted the code and created the service. The service are using an interface to start the service and now my problem is how to make use of the interface to make the service run.
In my shared project I put the interface and the viewmodel and in xamarin.android project I put the service.
The interface - IGeolocationBackgroundService:
public interface IGeolocationBackgroundService {
void StartService();
void StartTracking();
}
The viewmodel - GeolocatorPageViewModel:
public class GeolocatorPageViewModel
{
public Position _currentUserPosition { get; set; }
public string CoordinatesString { get; set; }
public List<string> userPositions { get; set; }
public ICommand StartTrackingCommand => new Command(async () =>
{
if (CrossGeolocator.Current.IsListening)
{
await CrossGeolocator.Current.StopListeningAsync();
}
CrossGeolocator.Current.DesiredAccuracy = 25;
CrossGeolocator.Current.PositionChanged += Geolocator_PositionChanged;
await CrossGeolocator.Current.StartListeningAsync(
TimeSpan.FromSeconds(3), 5);
});
private void Geolocator_PositionChanged(object sender, PositionEventArgs e)
{
var position = e.Position;
_currentUserPosition = position;
var positionString = $"Latitude: {position.Latitude}, Longitude: {position.Longitude}";
CoordinatesString = positionString;
Device.BeginInvokeOnMainThread(() => CoordinatesString = positionString);
userPositions.Add(positionString);
Debug.WriteLine($"Position changed event. User position: {CoordinatesString}");
}
}
The service - GeolocationService:
[assembly: Xamarin.Forms.Dependency(typeof(GeolocationService))]
namespace MyApp.Droid.Services
{
[Service]
public class GeolocationService : Service, IGeolocationBackgroundService
{
Context context;
private static readonly string CHANNEL_ID = "geolocationServiceChannel";
public GeolocatorPageViewModel ViewModel { get; private set; }
public override IBinder OnBind(Intent intent)
{
return null;
}
public GeolocationService(Context context)
{
this.context = context;
CreateNotificationChannel();
}
private void CreateNotificationChannel()
{
NotificationChannel serviceChannel = new NotificationChannel(CHANNEL_ID,
"GeolocationService", Android.App.NotificationImportance.Default);
NotificationManager manager = context.GetSystemService(Context.NotificationService) as NotificationManager;
manager.CreateNotificationChannel(serviceChannel);
}
//[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
var newIntent = new Intent(this, typeof(MainActivity));
newIntent.AddFlags(ActivityFlags.ClearTop);
newIntent.AddFlags(ActivityFlags.SingleTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, newIntent, 0);
var builder = new Notification.Builder(this, CHANNEL_ID);
var notification = builder.SetContentIntent(pendingIntent)
.SetSmallIcon(Resource.Drawable.ic_media_play_light)
.SetAutoCancel(false)
.SetTicker("Locator is recording")
.SetContentTitle("GeolocationService")
.SetContentText("Geolocator is recording for position changes.")
.Build();
StartForeground(112, notification);
//ViewModel = new GeolocatorPageViewModel();
return StartCommandResult.Sticky;
}
public void StartService()
=> context.StartService(new Intent(context, typeof(GeolocationService)));
public void StartTracking()
{
ViewModel = new GeolocatorPageViewModel();
ViewModel.StartTrackingCommand.Execute(null);
}
}
}
So be clear, I need to start the service and I'm not used to interfaces, so how do I call the interface?
use DependencyService to get a reference to your service and then start it
var svc = DependencyService.Get<IGeolocationBackgroundService>();
svc.StartService();
svc.StartTracking();

Android Service binding with MvvmCross

I am developing xamarin.Android app in MvvmCross. I want to call a service even when the App is backgrounded and a user is logged in. The problem is, I want to call this service within every say 2 hours whether the app is in foreground or background, just the user of the App needs to be logged in.
Intent loggedintent = new Intent(this,typeof(DeviceLoginHelper));
loggedintent.PutExtra("LoggedIn", true);
StartService(loggedintent);
I have written an android service:
[Service]
public class DeviceLoginHelper : IntentService
{
protected override void OnHandleIntent(Intent intent)
{
try
{
if(intent.HasExtra("LoggedIn"))
{
}
}
catch(Exception ex) { }
}
}
But how can I implement a timer? Where do I initialise and handle event to the timer. And when timer is elapsed when should I call ?
public override void OnDestroy()
{
try
{
base.OnDestroy();
}
catch(Exception ex){}
}
and when a user loges out i want to stop this service. Where do I put the call StopService() in MvvmCross
I would not use a Timer. Instead you should configure the AlarmManager.
[BroadcastReceiver]
public class AlarmReceiver : BroadcastReceiver
{
private static AlarmManager alarmMgr;
private static PendingIntent alarmIntent;
public const int NOTIFICATION_ID = 1;
public const int IDLE_TIME_MS = 30 * 1000; // 30-seconds (update here)
private NotificationManager mNotificationManager;
Notification.Builder builder;
public override void OnReceive(Context context, Intent intent)
{
// Do something when alarm triggers (here I'm building notification)
BuildNotification(context);
// reschedule alarm
ScheduleAlarm(IDLE_TIME_MS);
}
public static Context ApplicationContext { get; set; }
public static void ScheduleAlarm(int milliseconds)
{
if (milliseconds == 0) return;
alarmMgr = (AlarmManager)ApplicationContext.GetSystemService(Context.AlarmService);
var intent = new Intent(ApplicationContext, typeof(AlarmReceiver));
alarmIntent = PendingIntent.GetBroadcast(ApplicationContext, 0, intent, 0);
alarmMgr.Set(AlarmType.ElapsedRealtimeWakeup,
SystemClock.ElapsedRealtime() + milliseconds, alarmIntent);
}
private void BuildNotification(Context context)
{
mNotificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);
var contentIntent = PendingIntent.GetActivity(context, 0, new Intent(context, typeof(MainView)), 0);
var message = $"Time is up";
var mBuilder = new Notification.Builder(context)
.SetAutoCancel(true)
.SetPriority(NotificationCompat.PriorityMax)
.SetDefaults(NotificationDefaults.All)
.SetContentTitle("Time is up")
.SetStyle(new Notification.BigTextStyle()
.BigText(message))
.SetContentText(message)
.SetSmallIcon(Resource.Drawable.ic_launcher);
mBuilder.SetContentIntent(contentIntent);
mNotificationManager.Notify(NOTIFICATION_ID, mBuilder.Build());
}
}
In your startup code, simply call:
AlarmReceiver.ApplicationContext = context;
AlarmReceiver.ScheduleAlarm(timeInMs);

DisplayAlert With changing Text xamarin forms

I have a requirement where i have to show the status of the download on a DisplayAlert. But with changing text on it asynchronously.
How to achieve this?
DisplayAlert("Download Info", "Downloading.....", "Ok");
I want to show status like...
Connected to server
Downloading
Download Complete
Here is a simple "Dynamic Alert" for Forms and iOS using UIAlertController and Android using a DialogFragment and a Xamarin.Forms dependency service:
Dependency Interface:
public interface IDynamicAlert
{
void Show(string title, string message);
void Update(string message);
void Dismiss();
}
iOS IDynamicAlert Dependency Implementation:
public class DynamicAlert : IDynamicAlert
{
UIAlertController alert;
public void Show(string title, string message)
{
if (alert != null) throw new Exception("DynamicAlert already showing");
alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
var rootVC = UIApplication.SharedApplication.Windows[0].RootViewController;
rootVC.PresentViewController(alert, true, () =>
{
});
}
public void Update(string message)
{
if (alert == null) throw new Exception("DynamicAlert is not showing, call Show first");
alert.Message = message;
}
public void Dismiss()
{
if (alert == null) throw new Exception("DynamicAlert is not showing, call Show first");
alert.DismissViewController(true, () =>
{
alert.Dispose();
alert = null;
});
}
}
Example Usage:
var alert = DependencyService.Get<IDynamicAlert>();
if (alert != null)
{
alert.Show("StackOverflow", "Starting your request...");
await Task.Delay(2000); // Do some work...
alert.Update("Your request is processing...");
await Task.Delay(2000); // Do some work...
alert.Update("Your request is complete...");
await Task.Delay(750);
alert.Dismiss();
}
else
{
throw new Exception("IDynamicAlert Dependency not found");
}
Output:
Android Version:
The android version consists of a couple of parts, a DialogFragment subclass and the IDynamicAlert implementation that uses the custom DialogFragment.
Android DialogFragment Subclass:
public class DynamicAlertDialogFragment : DialogFragment
{
AlertDialog alertDialog;
readonly Context context;
public static DynamicAlertDialogFragment Instance(Context context, string title, string message)
{
var fragment = new DynamicAlertDialogFragment(context);
Bundle bundle = new Bundle();
bundle.PutString("title", title);
bundle.PutString("message", message);
fragment.Arguments = bundle;
return fragment;
}
public DynamicAlertDialogFragment(Context context)
{
this.context = context;
}
public override Dialog OnCreateDialog(Bundle savedInstanceState)
{
var title = Arguments.GetString("title");
var message = Arguments.GetString("message");
alertDialog = new AlertDialog.Builder(context)
.SetIcon(Android.Resource.Drawable.IcDialogInfo)
.SetTitle(title)
.SetMessage(message)
.Create();
return alertDialog;
}
public void SetMessage(string message)
{
(context as Activity).RunOnUiThread(() => { alertDialog.SetMessage(message);});
}
}
Android IDynamicAlert Dependency Implementation:
public class DynamicAlert : IDynamicAlert
{
const string FRAGMENT_TAG = "DynamicAlert_Fragment";
DynamicAlertDialogFragment fragment;
static FormsAppCompatActivity currentActivity;
public static FormsAppCompatActivity CurrentActivity { set { currentActivity = value; } }
public void Show(string title, string message)
{
if (currentActivity == null) throw new Exception("DynamicAlert.CurrentActivity needs assigned");
var fragMgr = currentActivity.FragmentManager;
var fragTransaction = fragMgr.BeginTransaction();
var previous = fragMgr.FindFragmentByTag(FRAGMENT_TAG);
if (previous != null)
{
fragTransaction.Remove(previous);
}
fragTransaction.DisallowAddToBackStack();
fragment = DynamicAlertDialogFragment.Instance(currentActivity, title, message);
fragment.Show(fragMgr, FRAGMENT_TAG);
}
public void Update(string message)
{
if (fragment == null) throw new Exception("DynamicAlert is not showing, call Show first");
fragment.SetMessage(message);
}
public void Dismiss()
{
if (fragment == null) throw new Exception("DynamicAlert is not showing, call Show first");
fragment.Dismiss();
fragment.Dispose();
fragment = null;
}
}
Android Init / Usage:
When creating the AlertDialog in the DialogFragment we need access to the current Activity and when using Xamarin.Forms, that is normally the MainActivity that is a FormsAppCompatActivity subclass. Thus you will need to initialize the DynamicAlert.CurrentActivity static property with this Activity in your MainActivity.OnCreate subclass:
Example:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
////////////
DynamicAlert.CurrentActivity = this;
////////////
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
Android Output:

Query Firebase Value OnChange

I am trying to register a Xamarin app to listen for value changes on a particular value change. I can access the value, but for some reason when I listen to the value change, it will fire once but never again until I reboot.
I am using Xamarin Studio, and FireSharp libraries. This is the API code in the C# library portion of the app. The reason I have removed the delegate was to check if it wasn't the delegate being cleaned up after the first call or something.
public class ValueAPI
{
private IFirebaseClient _client;
private ITemperatureListener _listener;
private EventStreamResponse _response;
public ValueAPI()
{
IFirebaseConfig config = new FirebaseConfig
{
AuthSecret = "...",
BasePath = "https://[value-api].firebaseio.com/"
};
_client = new FirebaseClient(config);
}
public async Task<string> getValue()
{
FirebaseResponse response = await _client.GetAsync("VALUE");
return response.Body;
}
public async Task<string> registerForUpdates(IValueListener listener)
{
_listener = listener;
_response = await _client.OnAsync("VALUE", null, this.OnValueChange, null, null);
return _response.ToString();
}
private void OnValueChange(object sender, ValueChangedEventArgs args, object context)
{
if (_listener != null)
{
_listener.OnValueUpdated(args.Data);
}
}
}
ANDROID CODE
ValueAPI api = new ValueAPI();
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
getValue();
getValueUpdates();
}
private async void getValue()
{
Task<string> task = api.getValue();
string result = await task;
TextView label = (TextView)FindViewById(Resource.Id.label_value);
label.Text = result;
}
private async void getValueUpdates()
{
Task<string> task = api.registerForUpdates(this);
await task;
}
public void OnValueUpdated(string value)
{
TextView label = (TextView)FindViewById(Resource.Id.label_value_updated);
label.Text = value;
}

Resources