I'm working in a xamarin forms app, and I'm trying to develop an Android notification with two buttons:
In the first one you can write text, and this text must be retrieve to one of the ViewModels.
The second one should open a view of the app.
I have no experience with intents nor Android, and so far I can show the notification with the two buttons:
notification example
The class in charge of showing the notification is LocalNotifications : ILocalNotifications class in the Android solution:
class LocalNotifications : ILocalNotifications
{
const string channelId = "default";
const string channelName = "Default";
const string channelDescription = "The default channel for notifications.";
...
bool channelInitialized = false;
int messageId = 0;
int replyPendingIntentId = 0;
int photoPendingIntentId = 0;
NotificationManager manager;
public event EventHandler NotificationReceived;
public static LocalNotifications Instance { get; private set; }
public LocalNotifications() => Initialize();
public void Initialize()
{
if (Instance == null)
{
CreateNotificationChannel();
Instance = this;
}
}
public void SendNotification(string title, string message)
{
if (!channelInitialized)
{
CreateNotificationChannel();
}
Show(title, message);
}
public void ReceiveNotification(string title, string message)
{
var args = new NotificationEventArgs()
{
Title = title,
Message = message,
};
NotificationReceived?.Invoke(null, args);
}
private static readonly string KEY_TEXT_REPLY = "key_text_reply";
AndroidX.Core.App.RemoteInput remoteEntryInput = new AndroidX.Core.App.RemoteInput.Builder(KEY_TEXT_REPLY)
.SetLabel("Escribir entrada")
.Build();
private Intent replyIntent;
private Intent photoIntent;
public void Show(string title, string message)
{
NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, channelId)
.SetContentTitle(title)
.SetContentText(message)
.SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.abc_ab_share_pack_mtrl_alpha))
.SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);
NotificationCompat.Action reply = CreateReplyIntent(title, message);
builder.AddAction(reply);
NotificationCompat.Action image = CreateImageIntent();
builder.AddAction(image);
Notification notification = builder.Build();
manager.Notify(messageId++, notification);
}
NotificationCompat.Action CreateReplyIntent(string title, string message)
{
//replyIntent = new Intent(AndroidApp.Context, typeof(MainActivity));
replyIntent = new Intent();
replyIntent.PutExtra(TitleKey, title);
replyIntent.PutExtra(MessageKey, message);
// Build a PendingIntent for the reply action to trigger.
PendingIntent replyPendingIntent =
PendingIntent.GetBroadcast(AndroidApp.Context, replyPendingIntentId++, replyIntent, PendingIntentFlags.UpdateCurrent);
// Create the reply action and add the remote input.
return new NotificationCompat.Action.Builder(Resource.Drawable.abc_ab_share_pack_mtrl_alpha,
"Escribir entrada", replyPendingIntent)
.AddRemoteInput(remoteEntryInput)
.Build();
}
NotificationCompat.Action CreateImageIntent()
{
//photoIntent = new Intent("android.media.action.IMAGE_CAPTURE");
photoIntent = new Intent(AndroidApp.Context, typeof(MainActivity));
//photoIntent.SetFlags( ActivityFlags.LaunchAdjacent | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Build a PendingIntent for the reply action to trigger.
PendingIntent phtoPendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, photoPendingIntentId++, photoIntent, PendingIntentFlags.UpdateCurrent);
return new NotificationCompat.Action.Builder(Resource.Drawable.abc_ab_share_pack_mtrl_alpha,
"Sacar foto", phtoPendingIntent)
.Build();
}
void CreateNotificationChannel()
{
manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelNameJava = new Java.Lang.String(channelName);
var channel = new NotificationChannel(channelId, channelNameJava, NotificationImportance.Default)
{
Description = channelDescription
};
manager.CreateNotificationChannel(channel);
}
channelInitialized = true;
}
}
Which a don't get is how/where the app can react to those PendintIntents and how to pass the information from the android solution to the xamarin forms model.
Related
I am trying to implement a feature where you choose a date and time and the notification pops up on your phone. so after writing some code its still not working but everything seems fine
Activity code
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
public void onClick(View view) {
Calendar customCalendar = GregorianCalendar.getInstance();
DatePicker dp = findViewById(R.id.date_picker);
TimePicker picker = findViewById(R.id.time_picker);
customCalendar.set(
dp.getYear(), dp.getMonth(), dp.getDayOfMonth(), picker.getHour(), picker.getMinute(), 0);
long customTime = customCalendar.getTimeInMillis();
SimpleDateFormat sdf = new SimpleDateFormat(getString(R.string.notification_schedule_pattern), Locale.getDefault());
long currentTime = System.currentTimeMillis();
Log.d("time", "cistomTime " + customTime);
Log.d("time", "cistomTime " + currentTime);
if (customTime > currentTime) {
Data data = new Data.Builder().putInt(NOTIFICATION_ID, 0).build();
int delay = (int) (customTime - currentTime);
scheduleNotification(delay, data);
String titleNotificationSchedule = getString(R.string.notification_schedule_title);
Snackbar.make(
view,
titleNotificationSchedule + sdf
.format(customCalendar.getTime()),
LENGTH_LONG).show();
// Snackbar.make(coordinatorLayout, "Reminder set", LENGTH_LONG)
// .setAction("Action", null).show();
} else {
String errorNotificationSchedule = "Error occured";
Snackbar.make(coordinatorLayout, errorNotificationSchedule, LENGTH_LONG).show();
}
}
});
}
private void scheduleNotification(long delay, Data data) {
OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotifyWork.class)
.setInitialDelay(delay, MILLISECONDS).setInputData(data).build();
WorkManager instanceWorkManager = WorkManager.getInstance(getApplicationContext());
instanceWorkManager.beginUniqueWork(NOTIFICATION_WORK, REPLACE, notificationWork).enqueue();
}
Worker class
public class NotifyWork extends Worker {
public static final String NOTIFICATION_ID = "notification_id";
public static final String NOTIFICATION_NAME = "Remember";
public static final String NOTIFICATION_CHANNEL = "Reminder_Channel";
public static final String NOTIFICATION_WORK = "Notification_Work";
public NotifyWork(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
int id = getInputData().getInt(NOTIFICATION_ID, 0);
sendNotification(id);
return Result.success();
}
private void sendNotification(int id) {
NotificationManager notificationManager = (NotificationManager) getApplicationContext()
.getSystemService(Context.NOTIFICATION_SERVICE);
Bitmap bitmap = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_done_white_24dp);
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(NOTIFICATION_ID, id);
String titleNotification = "Reminder";
String subtitleNotification = "Time To WakeUp";
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), NOTIFICATION_CHANNEL)
.setLargeIcon(bitmap).setContentTitle(titleNotification)
.setContentText(subtitleNotification).setDefaults(IMPORTANCE_DEFAULT).setSound(getDefaultUri(TYPE_NOTIFICATION))
.setContentIntent(pendingIntent).setAutoCancel(true);
notification.setPriority(IMPORTANCE_MAX);
notificationManager.notify(id, notification.build());
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Uri ringtoneManager = getDefaultUri(TYPE_NOTIFICATION);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE)
.setContentType(CONTENT_TYPE_SONIFICATION).build();
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL, NOTIFICATION_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(true);
channel.setLightColor(RED);
channel.enableVibration(true);
channel.setSound(ringtoneManager, audioAttributes);
notificationManager.createNotificationChannel(channel);
}
}
I have a DatePicker and TimePicker, when you select date and time and click on the FAB button, you get notified at that particular time
somehow changing .setLargeIcon to .setSmallIcon and referencing the image directly without converting to bitmap eg .setSmallIcon(R.drawable.ic_done_white_24dp) solved the issue
I completed receiving test notification from FCM console. Now I am trying to open a page when tapping the notification. Any ideas about how to achieve this? I have searched the internet but can't find a working solution. I am also able to send the notification through the postman.
I handle the notification tapping in following way. The page loading is handled in App.xaml.cs.
On OnCreate():
//Background or killed mode
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.GetString(key);
if (key == "webContentList")
{
if (value?.Length > 0)
{
isNotification = true;
LoadApplication(new App(domainname, value));
}
}
}
}
//Foreground mode
if (FirebaseNotificationService.webContentList.ToString() != "")
{
isNotification = true;
LoadApplication(new App(domainname, FirebaseNotificationService.webContentList.ToString()));
FirebaseNotificationService.webContentList = "";
}
//Normal loading
if (!isNotification)
{
LoadApplication(new App(domainname, string.Empty));
}
On FirebaseNotificationService:
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseNotificationService : FirebaseMessagingService
{
public static string webContentList = "";
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
webContentList = message.Data["webContentList"];
try
{
SendNotificatios(message.GetNotification().Body, message.GetNotification().Title);
}
catch (Exception ex)
{
Console.WriteLine("Error:>>" + ex);
}
}
public void SendNotificatios(string body, string Header)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Android.App.Notification.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentText(body)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, notificationBuilder.Build());
}
else
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Android.App.Notification.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentText(body)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent)
.SetChannelId(Utils.CHANNEL_ID);
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
var channel = new NotificationChannel(Utils.CHANNEL_ID, "FCM Notifications", NotificationImportance.High)
{
Description = "Firebase Cloud Messages appear in this channel"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
notificationManager.Notify(0, notificationBuilder.Build());
}
}
I don't know what's your actual Firebase implementation, but this might help you.
There is a nice package for Firebase in Xamarin Forms that we use in our production App made by CrossGeeks team. Its working great and has all handlers for your needs. This works with iOS and Android and you don't need to write platform specific code, only configuration and some code in AppDelegate.cs and MainActivity.cs
https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/FirebaseNotifications.md#notification-events
I wrote a simple PushNotificationService that processes automatic refresh and/or pushes new pages considering push notif data.
When app is closed and user clicks on the notification, I store the push notif data using Akavache.
CrossFirebasePushNotification.Current.OnNotificationOpened += async (s, p) =>
{
if (App.AppBeenResumed)
{
await BlobCache.UserAccount.InsertObject("pushNotifData", p.Data);
}
else
{
await ProcessReceivedPushNotification(p.Data);
}
};
And on the landing page of the app, I check if there is an existing push notif data in the OnAppearing method of the page.
protected override void OnAppearing()
{
base.OnAppearing();
App.AppBeenResumed = false;
HandlePushNotificationIfExists();
}
private async void HandlePushNotificationIfExists()
{
IDictionary<string, object> pushNotifData;
try
{
pushNotifData = await BlobCache.UserAccount.GetObject<IDictionary<string, object>>("pushNotifData");
}
catch (KeyNotFoundException)
{
pushNotifData = null;
}
if (pushNotifData == null) return;
await BlobCache.UserAccount.InvalidateAllObjects<IDictionary<string, object>>();
await PushNotificationService.ProcessReceivedPushNotification(pushNotifData);
}
In the ProcessReceivedPushNotification you can do whatever you want ... push directly the page or whatever... call another service that will do the job of pushing a new page and some business process.
Note that App.AppBeenResumed is a static bool to determine if App has been started or resumed to handle correctly the process of treatment of the push notif (process it instant or store it in blobcache to treat it later when landing page is Appearing).
In MainActivity.cs :
protected override void OnCreate(Bundle bundle)
{
...
LoadApplication(new App(true));
}
In the App.cs :
public App(bool beenResumedOrStarted)
{
...
AppBeenResumed = beenResumedOrStarted;
...
}
protected override void OnResume()
{
AppBeenResumed = false;
}
protected override void OnSleep()
{
//iOS states are not the same so always false when device is iOS
AppBeenResumed = Device.RuntimePlatform != Device.iOS;
}
I am trying to implement Stripe.net into my Xamarin.Forms PCL using an ASP.NET Core MVC Web API. The goal is to process credit card payment from users. My web API runs locally on http://localhost:port for testing purposes.
In the PaymentPage, a user enters their credit card information into Entry objects and when they click the submit Button, a method in the PaymentPageViewModel is called to start the logic:
async void OnFinishBookingClicked(object sender, System.EventArgs e)
{
// TODO: Stripe integration
var viewModel = (PaymentPageViewModel)this.BindingContext;
await viewModel.ProcessPayment();
}
This is part of the PaymentPageViewModel:
private readonly IStripeRepository _repository;
private readonly IAPIRepository _api;
public PaymentPageViewModel(IStripeRepository repository, IAPIRepository api)
{
_repository = repository;
_api = api;
}
public async Task ProcessPayment()
{
try
{
if (string.IsNullOrEmpty(ExpirationDate))
ExpirationDate = "09/18";
var exp = ExpirationDate.Split('/');
var token = _repository.CreateToken(CreditCardNumber, exp[0], exp[1], SecurityCode);
await Application.Current.MainPage.DisplayAlert("Test Message", token, "OK");
await _api.ChargeCard(token, 5.00M);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
}
This is what the APIRepository looks like:
public class APIRepository: IAPIRepository
{
const string Url = "http://localhost:5000";
private string authorizationKey;
private async Task<HttpClient> GetClient()
{
HttpClient client = new HttpClient();
if (string.IsNullOrEmpty(authorizationKey))
{
authorizationKey = await client.GetStringAsync(Url);
authorizationKey = JsonConvert.DeserializeObject<string>(authorizationKey);
}
client.DefaultRequestHeaders.Add("Authorization", authorizationKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");
return client;
}
public async Task<string> ChargeCard(string token, decimal amount)
{
HttpClient client = await GetClient();
var json = JsonConvert.SerializeObject(new { token, amount });
var response = await client.PostAsync("/api/Stripe", new StringContent(json));
return await response.Content.ReadAsStringAsync();
}
}
The issue is that I get a series of errors during await _api.ChargeCard(token, 5.00M):
The first exception happens during authorizationKey = await client.GetStringAsync(Url); the exception message is the following:
{System.Net.Http.HttpRequestException: 404 (Not Found) at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () [0x0000a] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/System.Net.Http/System.Net.Http/HttpResponseM…}
I get another exception during response = await client.PostAsync("/api/Stripe", new StringContent(json));
{System.InvalidOperationException: The request URI must either be an absolute URI or BaseAddress must be set at System.Net.Http.HttpClient.SendAsync (System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Thr…}
The third exception happens at the catch block of the viewModel.ProcessPayment() method:
{System.NullReferenceException: Object reference not set to an instance of an object at Zwaby.Services.APIRepository+d__3.MoveNext () [0x00184] in /Users/carlos/Projects/Zwaby/Zwaby/Services/APIRepository.cs:57 --- End of stack trace from previou…}
In my Web API project, I have a StripeController, but my implementation may not be fully correct:
[Route("api/Stripe")]
public class StripeController : Controller
{
private readonly StripeContext _context;
public StripeController(StripeContext context)
{
_context = context;
if (_context.StripeCharges.Count() == 0)
{
_context.StripeCharges.Add(new StripeItem { });
_context.SaveChanges();
}
}
[HttpGet]
public IActionResult Get(string key)
{
// TODO: implement method that returns authorization key
}
[HttpPost]
public IActionResult Charge(string stripeToken, decimal amount)
{
var customers = new StripeCustomerService();
var charges = new StripeChargeService();
var customer = customers.Create(new StripeCustomerCreateOptions
{
SourceToken = stripeToken
});
var charge = charges.Create(new StripeChargeCreateOptions
{
Amount = (int)amount,
Description = "Sample Charge",
Currency = "usd",
CustomerId = customer.Id
});
return View();
}
}
For completeness, I am including the StripeRepository class, the other parameter of the PaymentPageViewModel:
public class StripeRepository: IStripeRepository
{
public string CreateToken(string cardNumber, string cardExpMonth, string cardExpYear, string cardCVC)
{
StripeConfiguration.SetApiKey("my_test_key");
//TODO: Wireup card information below
var tokenOptions = new StripeTokenCreateOptions()
{
Card = new StripeCreditCardOptions()
{
Number = "4242424242424242",
ExpirationYear = 2018,
ExpirationMonth = 10,
Cvc = "123"
}
};
var tokenService = new StripeTokenService();
StripeToken stripeToken = tokenService.Create(tokenOptions);
return stripeToken.Id;
}
}
Thank you so much!
how to get Notification with out using google cloud . i thought of running a thread in back ground service and update the Notification but how i can i do it.pls help me out thanks in advance
public class BackgroundThread implements Runnable {
#Override
public void run() {
List<InvoiceDetail> invoiceDetails = JsonReader.getInvoice();
if (invoiceDetails.size() > 0) {
NotificationManager nfman = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
int notifyID = 1;
NotificationCompat.Builder builder = new NotificationCompat.Builder(
LoginActivity.this).setContentTitle("WORK ASSIGNED")
.setContentText("You've received new messages.");
builder.setContentText("hai");
nfman.notify(1, builder.build());
}
}
}
public class Notifier extends BroadcastReceiver {
#SuppressWarnings("deprecation")
#Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sPrefs = context.getSharedPreferences("loginData", 0);
String id = sPrefs.getString("id", "default value");
String userss = sPrefs.getString("user", "default value");
String pass = sPrefs.getString("pass", "default value");
if (id.trim().length() > 0) {
List<NotifierDetail> subjects = DataBase.notificationMethod(id);
if (subjects.size() > 0) {
User user = new User();
user.setUserName(userss);
user.setPassWord(pass);
User users = DataBase.login(user);
BaseConstants.user = users;
for (NotifierDetail sub : subjects) {
NotificationManager nm = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
R.drawable.logo, " Notification Today",
System.currentTimeMillis());
notification.defaults=Notification.DEFAULT_SOUND;
notification.defaults=Notification.DEFAULT_VIBRATE;
DataBase.updateNotificationMethod(sub.getId());
notification.flags = Notification.FLAG_AUTO_CANCEL;
Intent in = new Intent(context, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 0,
in, 0);
CharSequence name = "Notification";
CharSequence mess = name + " about " + sub.getSubject();
notification.setLatestEventInfo(context, name, mess, pi);
nm.notify(0, notification);
}
}
}
}
}
i want to generate a notification bar showing the progress via builder method but i dont know where i am going wrong.if anyone who can tell me where i am wrong and help me i will be thankful.....
public class DownloadReceiver extends ResultReceiver{
private final static String TAG = "DownloadReceiver";
public Context context;
public DownloadReceiver(Handler handler,Context context) {
super(handler);
this.context = context;
Log.d(TAG,handler.getLooper().getThread().getName());
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
Log.d(TAG,"in download receiver");
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
Intent notifyIntent = new Intent(android.content.Intent.ACTION_VIEW,Uri.parse("http://www.android.com"));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, 0);
if(resultCode == DownloadService.COMPLETED){
Log.d(TAG,resultCode + "");
Builder notificationBuilder = new NotificationCompat.Builder(context)
.setProgress(100, 20, false)
.addAction(R.drawable.ic_action_search, "title", pendingIntent)
.setWhen(System.currentTimeMillis());
// notification.flags = Notification.FLAG_ONGOING_EVENT;
// notification.setLatestEventInfo(context, "contentTitle", "contentText", pendingIntent);
notificationManager.notify(50, notificationBuilder.build());
}else if(resultCode == DownloadService.ALLCOMPLETED){
}
}
}
I just had to deal with this just now, the solution for me was that you have to add a notification image
.setSmallIcon(R.drawable.launcher)
otherwise it won't show anything. The old notification method didn't require you to set this yourself, as it would default to the app's icon.