I'm currently experiencing the following problem:
I've implemented custom FirebaseMessagingService and the method onMessageReceived() is overriden. Also when the app is in background i get the bundle from getExtras().
I need the notification content in order to save it locally in db.
What happens:
Send 3 notifications from Firebase console when the app is in background
3 status bar notifications are created.
Click on one of them -> launcher activity is opened and the content from the notification is saved.
Click on the other status bar notifications (when the app is still in foreground) -> nothing happens...
Could you please help?
Launcher Activity code:
if (getIntent().getExtras() != null) {
Bundle extras = getIntent().getExtras();
String title = (String) extras.get(Constants.TOPIC_KEY_TITLE);
String imageUrl = (String) extras.get(Constants.TOPIC_KEY_IMAGE_URL);
String url = (String) extras.get(Constants.TOPIC_KEY_URL);
String description = (String) extras.get(Constants.TOPIC_KEY_DESCRIPTION);
Long sentTime = (Long) extras.get(Constants.TOPIC_KEY_SENT_TIME);
if (Util.isStringsNotNull(description)) {
News news = new News();
news.setTitle(title);
news.setMessage(description);
news.setDescription(description);
news.setImageUrl(imageUrl);
news.setUrl(url);
news.setDate(sentTime);
news.save();
EventBus.getDefault().post(new OnNewsUpdatedEvent(news));
AppPreferences.incrementUnseenNewsCount(this);
}
}
String action = getIntent().getAction();
if (Util.isStringNotNull(action) && action.equals(ACTION_SEARCH)) {
startActivity(MainActivity.getIntentActionSearch(this));
} else {
startActivity(MainActivity.getIntent(this));
}
Custom FirebaseMessagingService code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
LogUtil.log(BASIC_TAG, "onMessageReceived called!");
String description = null;
String imageUrl = null;
String url = null;
String title = null;
Map<String, String> dataMap = remoteMessage.getData();
if (dataMap != null && !dataMap.isEmpty()) {
description = dataMap.get(Constants.TOPIC_KEY_DESCRIPTION);
imageUrl = dataMap.get(Constants.TOPIC_KEY_IMAGE_URL);
url = dataMap.get(Constants.TOPIC_KEY_URL);
title = dataMap.get(Constants.TOPIC_KEY_TITLE);
}
if (Util.isStringNotNull(description)) {
RemoteMessage.Notification notification = remoteMessage.getNotification();
News news = new News();
news.setDate(remoteMessage.getSentTime());
news.setTitle(Util.isStringNotNull(title) ? title : notification.getTitle());
news.setMessage(notification.getBody());
news.setDescription(description);
news.setImageUrl(imageUrl);
news.setUrl(url);
news.save();
EventBus.getDefault().post(new OnNewsUpdatedEvent(news));
AppPreferences.incrementUnseenNewsCount(this);
}
}
I'm going to assume you have your launcher activity code in the onCreate() method of your activity. Once the activity is created and you click another notification the onCreate() will not be called again.
What you need to do to update the activity that is already visible to the user is override the onNewIntent(Intent intent) method of the activity in which the data is displayed and update your views there.
Related
I am working in xamarin.forms. I am stuck in activity navigation in Android.
My whole application is working under one Activity that is MainActivity.
From one of my content page, I want to open Contact application with all list of contacts.
I have created one another activity and start that activity on button clicks from the Content page using Dependency service like below code.
var intent = new Intent(Forms.Context, typeof(ContactApplicationActivity));
Forms.Context.StartActivity(intent);
Now OnCreate method of ContactApplicationActivity I move to Contact application. And When the user clicks on any contact OnActivityResult method call and open message and Email default application.
ContactApplicationActivity.cs:
public class ContactApplicationActivity : Activity
{
public object smsTask { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
var contactPickerIntent = new Intent(Intent.ActionPick,
Android.Provider.ContactsContract.Contacts.ContentUri);
StartActivityForResult(contactPickerIntent, 101);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (InviteFriends.IsSMS && !InviteFriends.IsEmail)
{
if (requestCode == 101 && resultCode == Result.Ok)
{
//Ensure we have data returned
if (data == null || data.Data == null)
return;
var addressBook = new Xamarin.Contacts.AddressBook(this);
addressBook.PreferContactAggregation = false;
//Load the contact via the android contact id in the last segment of the Uri returned by the android contact picker
var contact = addressBook.Load(data.Data.LastPathSegment);
//Use linq to find a mobile number
var mobile = (from p in contact.Phones
where p.Type == Xamarin.Contacts.PhoneType.Mobile
select p.Number).FirstOrDefault();
//See if the contact has a mobile number
if (string.IsNullOrEmpty(mobile))
{
Toast.MakeText(this, "No Mobile Number for contact!",
ToastLength.Short).Show();
return;
}
else
{
if (CrossMessaging.Current.SmsMessenger.CanSendSms)
{
CrossMessaging.Current.SmsMessenger.SendSms(mobile, "My Message");
}
}
}
}
else if (!InviteFriends.IsSMS && InviteFriends.IsEmail)
{
//Ensure we have data returned
if (data == null || data.Data == null)
return;
var addressBook = new Xamarin.Contacts.AddressBook(this);
addressBook.PreferContactAggregation = false;
//Load the contact via the android contact id in the last segment of the Uri returned by the android contact picker
var contact = addressBook.Load(data.Data.LastPathSegment);
//Use linq to find a mobile number
var emailid = (from p in contact.Emails
where p.Type == Xamarin.Contacts.EmailType.Home
select p.Address).FirstOrDefault();
//See if the contact has a mobile number
if (string.IsNullOrEmpty(emailid))
{
Toast.MakeText(this, "No Email for contact!",
ToastLength.Short).Show();
return;
}
else
{
var email = BuildSampleEmail(emailid, true).Build();
if (CrossMessaging.Current.EmailMessenger.CanSendEmail)
{
CrossMessaging.Current.EmailMessenger.SendEmail(email);
}
}
}
}
public static EmailMessageBuilder BuildSampleEmail(string ToEmailId, bool sendAsHtml = false)
{
//var builder = new EmailMessageBuilder()
// .To(ToEmailId)
// .Cc("cc.plugins#xamarin.com")
// .Bcc(new[] { "bcc1.plugins#xamarin.com", "bcc2.plugins#xamarin.com" })
// .Subject("eRecall App");
var builder = new EmailMessageBuilder()
.To(ToEmailId)
.Subject("ABC");
if (sendAsHtml)
builder.BodyAsHtml("<b>My Message </b>");
return builder;
}
}
Everything is working fine. But the problem is when Contact application is open from ContactApplicationActivity Activity. MainActivity is in the background. When I try to open My original application from Background mode. It opens with a blank screen with Title ContactApplicationActivity. And When I press back button MainActivity with my last content page is open.
I want to open Last content page of my MainActivity without any blank screen
Is it possible to solve it using activity flag?
I'm currently working on a project where I received push notification on the device, and when taping the notification it should open a specific page.
I tried it on an Android 5.1, but when I tap the notification, it first opens the page and immediately after that it opens a blank page, whithout any navigation bar and I don't understand why...
Here is my code:
When I receive the the message on android I perform the following:
Intent intent = new Intent(this, typeof(MainActivity));
intent.PutExtra("key", "message");
PendingIntent pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
Notification notif = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Alert")
.SetContentText("alert message")
.SetAutoCancel(true)
.SetDefaults(NotificationDefaults.Sound | NotificationDefaults.Vibrate)
.SetContentIntent(pendingIntent)
.SetPriority((int)NotificationPriority.High)
.Build();
NotificationManager notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify(0, notif);
When taped it opens the MainActivity which does the following:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
if (app == null)
app = new App();
LoadApplication(app);
// If the user tapped a notification
if (Intent.Extras != null)
{
Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));
MessagingCenter.Send<Data>(data, "Show data");
}
}
Finally I have a DataPage (sets as the MainPage of the App, inside a NavigationPage) which does this:
public DataPage()
{
MessagingCenter.Subscribe<Data>(this, "Show data", (sender) =>
{
await Navigation.PushAsync(new DataDetail(sender));
});
}
I don't really understand why this does not work properly...
Especially since if I do
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(new DataPage(data)));
And create a second constructor in the DataPage:
DataPage(Data data)
{
await Navigation.PushAsync(new DataDetail(data));
}
It works fine. (But I don't like this, it looks very... not nice)
I can barely understand the connections between your Data, your DataDetail, your DataPage and your MainPage.
By my side, I think Data is like a data model based on your code Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));, but inside this class you code await Navigation.PushAsync(new DataDetail(data));, where does this Navigation come from?
Never mind, I guess that you have a MainPage wrapped by NavigationPage, and you want to navigate it to your DataPage when you click the notification. If so, then there is no problem with your Notification part.
To show the data in your DetailPage, you can create a constructor with parameter for example:
public partial class DataPage : ContentPage
{
public DataPage()
{
InitializeComponent();
}
public DataPage(int count)
{
InitializeComponent();
//set the data to the UI
label.Text = "This is Count: " + count;
}
}
You can create a single instance for Navigation in MainPage, so can it be used in Data class:
public static INavigation Navi;
public MainPage()
{
InitializeComponent();
Navi = this.Navigation;
}
Then my Data class is as simple as following:
public class Data
{
public Data()
{
MessagingCenter.Subscribe<Data>(this, "show data", (sender) =>
{
MainPage.Navi.PushAsync(new DataPage(this.Count));
});
}
public int Count { get; set; }
}
Finally in MainActivity:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
if (Intent.Extras != null)
{
int count = Intent.Extras.GetInt("count", -1);
var data = new Data() { Count = count };
MessagingCenter.Send<Data>(data, "show data");
}
}
You can modify the Data class as you need. The following is the rendering image of my demo:
Update:
Based on our discussion, your message is directly sent to DataPage, then in App class in PCL, wrap this DataPage with NavigationPage: MainPage = new NavigationPage(new DataPage());.
In the MainAcitvity send message like this:
if (Intent.Extras != null)
{
int count = Intent.Extras.GetInt("count", -1);
var data = new Data() { Count = count }; //fake data, change it to your data
var navipage = App.Current.MainPage as NavigationPage;
var datapage = navipage.CurrentPage as DataPage;
MessagingCenter.Send<DataPage, Data>(datapage, "show data", data);
}
And in the DataPage:
public DataPage()
{
InitializeComponent();
MessagingCenter.Subscribe<DataPage, Data>(this, "show data", (sender, arg) =>
{
Navigation.PushAsync(new DataDetail(arg));
});
}
Of course we need to create an instructor in DataDetail with parameter:
public DataDetail(Data data)
{
InitializeComponent();
}
So I finally solved the problem.
It seems that it was caused by the fact that I made a static App variable in the MainActivity in order to create it only once.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
static App app;
protected override void OnCreate(Bundle bundle)
{
...
if(app == null)
app = new App();
...
}
}
I did that because otherwise the Current.Properties of the app instance was reset.
Now I changed that and I no longer have this problem:
I call the Current.SavePropertiesAsync() to save the Current.Properties and therefore I can remove the static instance of App in the MainActivity. It solved the blank page problem.
But this made another problem. When I tapped the notification, it works correctly the first time, but after that it opened the requested page multiple times (The second notification tapped opened 2 pages, the third opened 3, ...). I don't understand why it did that, but it seemed to be caused by the MessagingCenter. So I remove the MessagingCenter part and simply did this in the MainActivity:
if (Intent.Extras != null)
{
Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));
DataPage currentPage = (DataPage)((NavigationPage)App.Current.MainPage).CurrentPage;
currentPage.OpenDataDetails(data);
}
And in the DataPage I have this function
public async void OpenDataDetails(Data data)
{
await Navigation.PushAsync(new DataDetail(data));
}
This does work as expected, so now I'm happy.
But still, I don't understand why when I had a static App instance in MainActivity it opened a blank page, nor why it opened multiple pages with the MessagingCenter...
I'm creating a notification management app and I want to get the contents of notifications which other apps show. currently I use codes like this :
statusBarNotification.getNotification().extras.getString(Notification.EXTRA_TITLE);
and this :
statusBarNotification.getNotification().extras.getString(Notification.EXTRA_TEXT);
to read the title and text of notifications. but after a few hours I couldn't find a way to get the image which comes along with the notification's text. for example a profile picture which is showed in Whatsapp's notification. I know it's not the Small or Large icons, I checked a few times.
So if anyone could help in any way, it would be much appreciated
I assume you use NotificationListenerService to listen to notification from other app.
In you NotificationService class, extract icon ressource id in extra Notification.EXTRA_SMALL_ICON and access the other app package ressources to get the Drawable.
Notification.EXTRA_PICTURE contains the large image sent in the notification :
public class NotificationService extends NotificationListenerService {
Context context;
#Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
#Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
// a notification is posted
String pack = statusBarNotification.getPackageName();
Bundle extras = statusBarNotification.getNotification().extras;
int iconId = extras.getInt(Notification.EXTRA_SMALL_ICON);
try {
PackageManager manager = getPackageManager();
Resources resources = manager.getResourcesForApplication(pack);
Drawable icon = resources.getDrawable(iconId);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (extras.containsKey(Notification.EXTRA_PICTURE)) {
// this bitmap contain the picture attachment
Bitmap bmp = (Bitmap) extras.get(Notification.EXTRA_PICTURE);
}
}
#Override
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
//call when notification is removed
}
}
My app has one activity. The app has a drawer that has a list that is filled from my content provider. From the drawer the user can select an item and then the Activity will be filled with the appropriate content dynamically. I am not sure how to implement app indexing in such a case. I mean based on step 3 of the tutorial, the activity seems to be expected to show one content (am I wrong about this)?
Note: I already got deep linking working ( I have a website and the content map to the content in the app).
Specifically I am wondering to I dynamically change the following each time the user changes the content:
mUrl = "http://examplepetstore.com/dogs/standard-poodle";
mTitle = "Standard Poodle";
mDescription = "The Standard Poodle stands at least 18 inches at the withers";
And if yes, how about the fact that I am only supposed to make the call once (in onStart only). And again, my data is loaded from a content provider. The provider itself is loaded from the server, but that call loads everything -- as opposed to just loading a single page.
AFAIK, you should connect your GoogleApiClient once per activity only. However, you can index your dynamical content as much as you want (but better to not index content too many times), just remember to disconnect them when your activity finish. Below is what I did in my project:
HashMap<String, Action> indexedActions;
HashMap<String, Boolean> indexedStatuses;
public void startIndexing(String mTitle, String mDescription, String id) {
if (TextUtils.isEmpty(mTitle) || TextUtils.isEmpty(mDescription))
return; // dont index if there's no keyword
if (indexedActions.containsKey(id)) return; // dont try to re-indexing
if (mClient != null && mClient.isConnected()) {
Action action = getAction(mTitle, mDescription, id);
AppIndex.AppIndexApi.start(mClient, action);
indexedActions.put(id, action);
indexedStatuses.put(id, true);
LogUtils.e("indexed: " + mTitle + ", id: " + id);
} else {
LogUtils.e("Client is connect : " + mClient.isConnected());
}
}
public void endIndexing(String id) {
// dont endindex if it's not indexed
if (indexedStatuses.get(id)) {
return;
}
if (mClient != null && mClient.isConnected()) {
Action action = indexedActions.get(id);
if (action == null) return;
AppIndex.AppIndexApi.end(mClient, action);
indexedStatuses.put(id, false);
}
}
I have an interesting issue that I have been trying to fix for over a week. Its on Android and involves a service running in the background to send a message on an app.
It is quite complex so I'll list the stages below:
1 - User enters message
2 - User selects 'send' button which launches the apps main service (ComService/START_STICKY) and activity (HomeScreen) hides the EditText box used for the message, replacing it with a TextView with the words 'Sending'
3 - Service spawns worker thread
4 - Service gets entered text off of activity and connects to server
5 - Service sends the message and then gets result from server
6 - Service disconnects from server
7 - Service updates activity to show sent message and shows the edit text
box again, as well as hides the TextView with the 'Sending' word
The issue is with stage 7. It uses a handler and message to communicate with the activity, as well as a separate class which holds the state of all of the activities in the app (to check if the UI is ok to update), but I commented this out from the code and the issue still exists so it is not this. The current set up works completely fine when the debugger is attached without any issues (why there is no logcat) and on the odd occasion when closing the app down and starting it up again. The problem begins when the debugger is detached and the app closed (via recent apps) for over around 5 seconds. The service completed its job by sending the message, as the message is added to the database and the user on the other end gets it, it is only the updating of the UI that is a problem, everything else seems to work fine!
Before sending the message, the service also connects to the server if there are any unread messages to indicate to other users that the user just read it. It follows very similar steps as above but was commented out and the issue still stays the same.
Ill post the code for the relevant steps below:
Stage 2
// Starts service to communicate with the server to send a message
Intent service = new Intent(this, ComService.class);
service.putExtra(ComService.requestType, ComService.sendTextMessage);
startService(service);
Stage 3
// Run when the service is being created
#Override
public int onStartCommand(Intent newIntent, int flags, int startId)
{
intent = newIntent;
currentInstance = this;
// Launches processing thread
ServiceHelper serviceHelper = new ServiceHelper(newIntent, this);
serviceHelper.start();
return Service.START_STICKY;
}
Stage 4/5/6/7
// Sends a message with only text content
public void startText()
{
// Initialises the class holding activity data
StateManager sm = new StateManager(context);
// Gets the data ready to be sent
if(sm.getHomeScreen())
{
// Friend id and time
friendId = HomeScreen.getFriendId();
// Gets the message text
messageTextContent = HomeScreen.getTextMessage(); // STAGE 4
}
else
{
allGood = false;
}
try
{
// Checks if any errors
if(allGood)
{
// Checks if message to be sent is blank
if(!messageTextContent.equals("") & messageTextContent.equals(" ") & !(messageTextContent == "") & !(messageTextContent == " "))
{
// Connects
(sh.new Utility()).connect(); // STAGE 4
// Checks if logged in
if((sh.new CheckLogin()).start())
{
// Sends request
sh.getNetwork().sendData(ServiceHelper.sendMessageTextRequest);
// Sends the friend id who message is addressed to
sh.getNetwork().sendData(friendId);
// Gets message date
messageDate = sh.getNetwork().getDataAsString();
// Sends the message type
sh.getNetwork().sendData(textType);
// Sends the message text
sh.getNetwork().sendData(messageTextContent); // STAGE 5
// Gets the message number
messageNumber = Integer.parseInt(sh.getNetwork().getDataAsString());
// Gets result and check if successful
String result = sh.getNetwork().getDataAsString(); // STAGE 5
if(!result.equals(ServiceHelper.requestSuccessful))
{
// Not successful
allGood = false;
errorMessage = result;
}
else
{
// Successful and saves data to database
addDatabaseTextMessage();
}
// Sends received indicator
sh.getNetwork().sendData(ServiceHelper.receivedIndicator);
}
else
{
allGood = false;
errorMessage = tryAgainMsg;
}
// Closes connection
(sh.new Utility()).finishConnection(); // STAGE 6
// Gets current friend record
UserDatabase db = new UserDatabase(context);
FriendRecord fr = db.getFriendRecord(Integer.parseInt(friendId));
// Increments message numbers by one and updates database
fr.setTotalExchanged(fr.getTotalExchanged() + 1);
db.updateFriendRecord(fr);
}
else
{
allGood = false;
errorMessage = msgBlank;
}
}
else
{
allGood = false;
errorMessage = tryAgainMsg;
}
} catch(IOException e)
{
allGood = false;
errorMessage = checkConnectionMsg;
// Adds to log cat
Log.e(this.getClass().getSimpleName(), e.getMessage());
} catch(Exception e2)
{
allGood = false;
errorMessage = tryAgainMsg;
// Adds to log cat
Log.e(this.getClass().getSimpleName(), e2.getMessage());
}
// Decides on result
if(allGood)
{
// Refreshes the ui
if(sm.getHomeScreen()) // STAGE 6
{
Message msg = HomeScreen.homeScreenInterface.obtainMessage(HomeScreen.sendSuccess); // STAGE 7
HomeScreen.homeScreenInterface.sendMessage(msg); // STAGE 7
}
}
else
{
// Indicates error on ui
if(sm.getHomeScreen())
{
Message msg = HomeScreen.homeScreenInterface.obtainMessage(HomeScreen.messageSendError); // STAGE 7
msg.obj = errorMessage; // STAGE 6
HomeScreen.homeScreenInterface.sendMessage(msg); // STAGE 7
}
}
}
State 7 (updating UI)
// Deals with service responses
public static Handler homeScreenInterface = new Handler()
{
#Override
public void handleMessage(Message msg)
{
switch(msg.what)
{
...
...
case HomeScreen.sendSuccess:
// Enables and clears input
HomeScreen.messageInput.setEnabled(true);
HomeScreen.messageInput.setText("");
// Shows the message send menu and hides sending text
HomeScreen.sendMenu.setVisibility(View.VISIBLE);
HomeScreen.sendingText.setVisibility(View.GONE);
// Clears message variable
message = "";
attachment = null;
try
{
// Reloads the message list
messageFriendId = "";
currentInstance.loadMessageList(true);
} catch (Exception e)
{
currentInstance.loadingScreen.open("Error", "Try again later!", true, "Ok", currentInstance);
}
// Checks if the message is sending
isMessageSending = false;
break;
...
...
}
}
};
Try using Broadcast Receiver instead of a handler to refresh your ui.
http://developer.android.com/reference/android/content/BroadcastReceiver.html
private BroadcastReceiver bReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("UPDATE")) {
String text = intent.getStringExtra("output");
updateUI(text);
}
else if(intent.getAction().equals("RESTART")) {
//other stuff
Board.this.startService(i);
}
}
};
//from Service
Intent intent = new Intent();
intent.setAction("UPDATE");
intent.putExtra("output",modifiedSentence);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
//onStart
LocalBroadcastManager bManager = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction("UPDATE");
filter.addAction("RESTART");
bManager.registerReceiver(bReceiver, filter);
//onPause
LocalBroadcastManager bManager = LocalBroadcastManager.getInstance(this);
bManager.unregisterReceiver(bReceiver);