MAUI send sms without user interaction in Maui - sms

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

Related

What is the equivalent of Xamarin MessagingCenter in Flutter?

With Xamarin, We usually use Message-Center to send a message to any page when Application is running (start App or app is background).
With Flutter, have we any ways that send a message same with Message-Center of Xamarin.Forms?
Based on my research, you can use Channel to achieve that.
BasicMessageChannel is a similar channel.
Native part.
private final Activity activity;
private final BasicMessageChannel<String> messageChannel;
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
private BasicMessageChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
this.messageChannel = new BasicMessageChannel<>(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE);
//Set up a message handler to handle messages from Dart
messageChannel.setMessageHandler(this);
}
#Override
public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {//Handle messages from Dart
reply.reply("BasicMessageChannel:" + s);//Can reply by `reply`
if (activity instanceof IShowMessage) {
((IShowMessage) activity).onShowMessage(s);
}
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
/**
* Send Dart a message and receive feedback from Dart
*
* #param message Content of the message to send to Dart
* #param callback Feedback from Dart
*/
void send(String message, BasicMessageChannel.Reply<String> callback) {
messageChannel.send(message, callback);
}
#Override
public void reply(String s) {
}
}
Dart Part
import 'package:flutter/services.dart';
static const BasicMessageChannel _basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec());
//Use BasicMessageChannel to receive messages from Native and reply to Native
_basicMessageChannel
.setMessageHandler((String message) => Future<String>(() {
setState(() {
showMessage = message;
});
return "receive Native's message:" + message;
}));
//use BasicMessageChannelsend message to Native And accept Native's reply
String response;
try {
response = await _basicMessageChannel.send(value);
} on PlatformException catch (e) {
print(e);
}
Here is blog about it.
https://stablekernel.com/flutter-platform-channels-quick-start/
I've been searching the same implementation in Flutter. So far I haven't found any.
This is my simple implementation
import 'package:meta/meta.dart';
abstract class MessagingService {
void subscribe(Object subscriber, {#required String channel, #required void Function(Object) action});
void unsubscribe(Object subscriber, {#required String channel});
void send({#required String channel, Object parameter});
}
class MessagingServiceImpl implements MessagingService {
static final _map = <String, Map<String, void Function(Object)>>{};
#override
void subscribe(Object subscriber, {#required String channel, #required void Function(Object) action}) {
assert(subscriber != null);
assert(channel != null);
assert(channel.isNotEmpty);
assert(action != null);
if (!_map.containsKey(channel)) {
_map[channel] = {};
}
_map[channel].putIfAbsent(subscriber.hashCode.toString(), () => action);
}
#override
void send({#required String channel, Object parameter}) {
assert(channel != null);
assert(channel.isNotEmpty);
if (_map.containsKey(channel)) {
for (final action in _map[channel].values) {
action(parameter);
}
}
}
#override
void unsubscribe(Object subscriber, {#required String channel}) {
if (_map.containsKey(channel)) {
_map[channel].removeWhere((k, v) => k == subscriber.hashCode.toString());
}
}
}

Toast is shown every time when device is rotate

In my Android app I use AAC.
Here my activity:
public class AddTraderActivity extends AppCompatActivity {
AddTraderViewModel addTraderViewModel;
private static final String TAG = AddTraderActivity.class.getName();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AddTraderActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.add_trader_activity);
binding.setHandler(this);
init();
}
private void init() {
ViewModelProvider viewViewModelProvider = ViewModelProviders.of(this);
addTraderViewModel = viewViewModelProvider.get(AddTraderViewModel.class);
Observer<String> () {
#Override
public void onChanged (String message){
Debug.d(TAG, "onChanged: message = " + message);
Toast.makeText(AddTraderActivity.this, message, Toast.LENGTH_LONG).show();
}
});
}
public void onClickStart() {
EditText baseEditText = findViewById(R.id.baseEditText);
EditText quoteEditText = findViewById(R.id.quoteEditText);
addTraderViewModel.doClickStart(baseEditText.getText().toString(), quoteEditText.getText().toString());
}
}
Here my ViewModel:
public class AddTraderViewModel extends AndroidViewModel {
private MutableLiveData<String> messageLiveData = new MutableLiveData<>();
private static final String TAG = AddTraderViewModel.class.getName();
public AddTraderViewModel(#NonNull Application application) {
super(application);
}
public void doClickStart(String base, String quote) {
Debug.d(TAG, "doClickStart: ");
if (base.trim().isEmpty() || quote.trim().isEmpty()) {
String message = getApplication().getApplicationContext().getString(R.string.please_input_all_fields);
messageLiveData.setValue(message);
return;
}
}
public LiveData<String> getMessageLiveData() {
return messageLiveData;
}
}
So when I click on button on Activity call method onClickStart()
If any fields is empty the show toast. In the activity call method:
onChanged (String message)
Nice. It's work fine.
But the problem is, when I rotate the device in the activity method onChanged(String message) is called AGAIN and as result show toast. This happened on every rotation.
Why?
This is the expected behaviour. If you want to avoid this you must set message = "" and keep an empty check before showing the toast.
A better way to use it is something like Event Wrapper or SingleLiveEvent
Highly recommend you to read this article. This explains why you are facing this and what are your options in detail.

Xamarin.Forms how open default email client on device?

In Xamarin.Forms if you want to open the device's default browser by tapping a Label with a link, it's simple as this:
private void WebUrl_TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var label = sender as Label;
string url = "http://" + label.Text;
Device.OpenUri(new Uri(url));
}
Is there a similarly simple way to open the device's default email client with an open NewMessage with email address?
private void EmailClient_TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var label = sender as Label;
// what goes here?
}
Thank you.
Try with:
var address = "your.address#gmail.com";
Device.OpenUri(new Uri($"mailto:{address}"));
Hope this helps.-
I actually use a Dependency Service so that I have more control over what I can send to mail client.
First I created an interface to be used by the dependency service called IEmailService.
public interface IEmailService
{
void CreateEmail(List<string> emailAddresses, List<string> ccs, string subject, string body, string htmlBody);
}
My Dependency Service for Android looks like this:
[assembly: Xamarin.Forms.Dependency(typeof(EmailService))]
namespace Droid.Services
{
public class EmailService : IEmailService
{
public void CreateEmail(List<string> emailAddresses, List<string> ccs, string subject, string body, string htmlBody)
{
var email = new Intent(Android.Content.Intent.ActionSend);
if (emailAddresses?.Count > 0)
{
email.PutExtra(Android.Content.Intent.ExtraEmail, emailAddresses.ToArray());
}
if (ccs?.Count > 0)
{
email.PutExtra(Android.Content.Intent.ExtraCc, ccs.ToArray());
}
email.PutExtra (Android.Content.Intent.ExtraSubject, subject);
email.PutExtra (Android.Content.Intent.ExtraText, body);
email.PutExtra (Android.Content.Intent.ExtraHtmlText, htmlBody);
email.SetType ("message/rfc822");
MainActivity.SharedInstance.StartActivity(email);
}
}
}
For iOS:
[assembly: Xamarin.Forms.Dependency(typeof(EmailService))]
namespace iOS.Services
{
public class EmailService : NSObject, IEmailService, IMFMailComposeViewControllerDelegate
{
public void CreateEmail(List<string> emailAddresses, List<string> ccs, string subject, string body, string htmlBody)
{
var vc = new MFMailComposeViewController();
vc.MailComposeDelegate = this;
if(emailAddresses?.Count > 0)
{
vc.SetToRecipients(emailAddresses.ToArray());
}
if(ccs?.Count > 0)
{
vc.SetCcRecipients(ccs.ToArray());
}
vc.SetSubject(subject);
vc.SetMessageBody(htmlBody, true);
vc.Finished += (sender, e) =>
{
vc.DismissModalViewController(true);
};
UIApplication.SharedApplication.Windows[0].
RootViewController.PresentViewController(vc, true, null);
}
}
}
Then I can just call this in my code:
DependencyService.Get<IEmailService>().CreateEmail(recipients, ccs, subject, body, bodyHtml);
This will open the mail client on each platform with the to, subject and body fields optionally poplulated.
I hope that helps.
You can use Launcher.OpenAsync(uri) exists in Xamarin.Essentials. OpenUri is obsolete as of version 4.3.0. uri = $"mailto:{address}?subject={emailSubject}&body={body content}";

android media picker from Xamarin forms

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

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:

Resources