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...
Related
I am trying to pass information from the MainActivity.cs to a View and then update that view with the information to that view in some way. In this case an image. Problem is I cannot get the View to update from within the subscribed message function. The function does hit a break point but doesnt update the UI.
Ive added an intentfilter to MainActivity.cs, and sent the stream of the image via a MessagingCenter using the "Xamarin.Forms.Application.Current" (Is there a better way/object to use?).
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
if (Intent.Action == "android.intent.action.SEND")
{
var data = Intent.Data;
Android.Net.Uri imageUri = (Android.Net.Uri)Intent.GetParcelableExtra(Android.Content.Intent.ExtraStream);
var stream = ContentResolver.OpenInputStream(imageUri);
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ImageSent", stream);
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
On my view I accept the information from the message subscription and have a function that runs. It does hit a break point.
public partial class AboutPage : ContentPage
{
AboutViewModel _viewModel;
public AboutPage()
{
InitializeComponent();
MessagingCenter.Instance.Subscribe<Application, Stream>(Application.Current, "ImageSent", OnImageSent);
BindingContext = _viewModel = new AboutViewModel();
}
private void OnImageSent(Application source, Stream imageStream)
{
stack.Children.Add(new Image
{
Source = "xamarin_logo.png",
VerticalOptions = LayoutOptions.Center
});
}
}
If I add the dynamic image (ive hard coded the image to an image I know exists in this instance) on the constructor it adds it fine but not if it runs in the function. I understand that I should be using MVVM etc, but I want to get this working simply with a minimum of moving parts and then take it from there. Problem exists when I update the BindingContext object too. Something to do with the context of the function that gets run?
I also tried the suggestion to try add the image inside Device.BeginInvokeOmMainThread. But this didnt work either.
private void OnImageSent(App source, Stream imageStream)
{
Device.BeginInvokeOnMainThread(() =>
{
stack.Children.Add(new Image
{
//HeightRequest = 64,
Source = "xamarin_logo.png",
VerticalOptions = LayoutOptions.Center
});
});
}
Or am I going about this the wrong way entirely? How should I get my shared image to the App to my view?
Convert Stream to ImageSource and try.
private void OnImageSent(Application source, Stream imageStream)
{
var imageSource = ImageSource.FromStream(() => imageStream);
stack.Children.Add(new Image
{
Source = imageSource,
VerticalOptions = LayoutOptions.Center,
WidthRequest = 100,
HeightRequest = 100,
}); ;
}
Note :- I have noticed, in debug mode its not working. Try launch the application by selecting a photo from gallery and open.
Let me know if not working. I will share the code.
I just wanted to prove I could update the UI from the subscribe using an image
Yes, it works using an image.
am I going about this the wrong way entirely? How should I get my
shared image to the App to my view?
It's fine using MessagingCenter. You can use Observer pattern also.
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 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.
How can I run a Xamarin.Forms.Platform.Android.FormsApplicationActivity on an Android Wear device? The call base.OnCreate(bundle) inside the onCreate method of my class always throws a RuntimeException "You cannot use indeterminate progress on a watch".
Here is my code:
namespace Test
{
[Activity (Label = "Temp.Droid", Icon = "#drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.Init (this, bundle);
LoadApplication (new App ());
}
}
}
The implementation of App should not matter since the exception gets already thrown on the call of the super onCreate and not by calling LoadApplication (new App ()) for loading the application. However its the base implementation generated by the project wizard for a Xamarin Mobile Application.
Despite the answer of James Montemagno I discovered it is possible to sync data in Xamarin Forms. I used the method of Vincent Maverick and incorporated it in Xamarin Forms. First of all take care you have the right Android SDK installed (Android Wear Tutorial - A Comprehensive Introduction).
Assuming you hve your standard app, it is advised to create Wear app in a separate Xamarin Forms Cross Platform application. This because the Wear sizes are different from Phone sizes.
In both you Wear app and your phone app right click on the References of your Android project and select MANAGE NUGET PACKAGES. Browse for wear and select
Xamarin.GooglePlayServices.Wearable Version 29.0.0 (higher versions give problems).
Click on the Properties of your Android project in both applications. Make sure the Default Namespace (Application tab) and Package name (Android Manifest tab) are the same. Also make sure the Package name does not have capitals, this will cause problems in releasing your app to the Android store.
Change the value of "Compile using Android version" to "API Level 21 (Xamarin.Android v5.0 Support).
In your Android MainActivity of both projects add usings
using Android.Gms.Wearable;
using Android.Gms.Common.Apis;
using Android.Support.V4.Content;
Then change your MainActivity of both applications to the following:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IDataApiDataListener, IMessageApiMessageListener
{
private static GoogleApiClient client;
const string _syncPath = "/MySyncPath/Data";
static string device = "Watch";
static string text= "";
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());
client = new GoogleApiClient.Builder(this)
.AddApi(WearableClass.API)
.Build();
IntentFilter filter = new IntentFilter(Intent.ActionSend);
MessageReciever receiver = new MessageReciever(this);
LocalBroadcastManager.GetInstance(this).RegisterReceiver(receiver, filter);
}
internal class MessageReciever : BroadcastReceiver
{
MainActivity _main;
public MessageReciever(MainActivity owner) { this._main = owner; }
public override void OnReceive(Context context, Intent intent)
{
_main.ProcessMessage(intent);
}
}
public void OnDataChanged(DataEventBuffer dataEvents)
{
var dataEvent = Enumerable.Range(0, dataEvents.Count)
.Select(i => dataEvents.Get(i).JavaCast<IDataEvent>())
.FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath));
if (dataEvent == null)
return;
//do stuffs here
}
public override void OnBackPressed()
{
base.OnBackPressed();
}
protected override void OnStart()
{
base.OnStart();
Android.Util.Log.Info("WearIntegration", "Received Message");
client.Connect();
}
public void OnConnected(Bundle p0)
{
WearableClass.DataApi.AddListener(client, this);
}
public void OnConnectionSuspended(int reason)
{
Android.Util.Log.Error("GMSonnection suspended " + reason, "");
WearableClass.DataApi.RemoveListener(client, this);
}
public void OnConnectionFailed(Android.Gms.Common.ConnectionResult result)
{
Android.Util.Log.Error("GMSonnection failed " + result.ErrorCode, "");
}
protected override void OnStop()
{
base.OnStop();
client.Disconnect();
}
public void OnMessageReceived(IMessageEvent messageEvent)
{
if (messageEvent.Path.Equals(_syncPath))
{
var msg = System.Text.Encoding.UTF8.GetString(messageEvent.GetData());
this.RunOnUiThread(() =>
Android.Widget.Toast.MakeText(this, msg, ToastLength.Long).Show());
}
}
public void ProcessMessage(Intent intent)
{
if (intent.GetStringExtra("Device") != device)
{
text = intent.GetStringExtra("WearMessage");
//do stuffs here
}
}
public void SendData() {
try {
var request = PutDataMapRequest.Create(_syncPath);
var map = request.DataMap;
map.PutString("Device", device);
map.PutString("Message", "Xamarin Forms says Hello from Wearable!");
map.PutLong("UpdatedAt", DateTime.UtcNow.Ticks);
WearableClass.DataApi.PutDataItem(_client, request.AsPutDataRequest());
}
finally {
_client.Disconnect();
}
}
In your Phone application change the static string device to Phone and change the message text if you want to:
static string device = "Phone";
map.PutString("Message", "Xamarin Forms says Hello from Phone!");
Then add the WearService class to both your Android Projects add the same usings as added to the MAinActivity an change the Wearservice as follows:
[Service]
[IntentFilter(new[] { "com.google.android.gms.wearable.BIND_LISTENER" })]
public class WearService : WearableListenerService
{
const string _syncPath = "/KorfballTimer/Data";
GoogleApiClient _client;
public override void OnCreate()
{
base.OnCreate();
_client = new GoogleApiClient.Builder(this.ApplicationContext)
.AddApi(WearableClass.API)
.Build();
_client.Connect();
Android.Util.Log.Info("WearIntegrationreated", "");
}
public override void OnDataChanged(DataEventBuffer dataEvents)
{
var dataEvent = Enumerable.Range(0, dataEvents.Count)
.Select(i => dataEvents.Get(i).JavaCast<IDataEvent)
.FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath));
if (dataEvent == null)
return;
//get data from wearable
var dataMapItem = DataMapItem.FromDataItem(dataEvent.DataItem);
var map = dataMapItem.DataMap;
string message = dataMapItem.DataMap.GetString("Message");
Intent intent = new Intent();
intent.SetAction(Intent.ActionSend);
intent.PutExtra("WearMessage", message);
intent.PutExtra("Device", map.GetString("Device"));
LocalBroadcastManager.GetInstance(this).SendBroadcast(intent);
}
}
And finally, Add the meta data in the AndroidManifest.xml under element:
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version" />
If you don't want the IOS and Windows projects in your Wear application, just delete them. Now you can build your Wear application in Xamarin Forms just as you do with your phone application. Happy Coding.
You would not run a Xamarin.Forms application on a wearable device. You would need to create a new Android Wear application in native Xamarin.Android. Wearable Applications use a special theme, special controls, and have special APIs. A good sample to look at is how I did Hanselman.Forms, which is a Xamarin.Forms main application but ties in an Android Wear application as well: https://github.com/jamesmontemagno/Hanselman.Forms
I need to implement functionality to display application news/announcements for all users.
I will do it rarely so push notification is not for me.
Are there any library or sample, how to implement this fast and easy? Or better will be to implement it in next way:
When application starts - read html file on server, if file was updated after the last attempt - display content of file in popup.
All you need is to load an html, or better, a simple text file containing the notification index like:
in yourdomain.com/notificationnumber.txt
contains
"4"
Check in your applications, probably simply using SharedPreferences for the last displayed version. Then load all notification, for example:
yourdomain.com/notification3.html
yourdomain.com/notification4.html
Display them and store the index number to know which notifcations have been shown to the user. There are no special libraries needed for such a task.
If the announcements are not very frequent, then checking the html file on the server each time the application is launched is good enough.
I feel like it depends on the styling options, which your notifications require:
if no rich styling is needed, you could simply check an XML-file, Text-file or similar for some notification information (Texts, notification coded) as bjornson proposed and then just show an AlertDialog with the specific information.
If you require rich styling, you may need to load the content from a server by utilizing a WebView. So for example your app's frontpage could partly consist of a WebView that in specific cases shows the news, which you intend to present to the user. Please note, that the WebView-technique is/was used by Facebook to update certain parts of their apps without the troubles of releasing a new version in the various App Stores.
I have used an rss-feed from a wordpress blog for this, if there is a new entry, a webview is opened and the new entry is displayed. Instead of displaying the entry, one could just parse the data and display ist (using jdom or other libraries to read the html document) We use IntentService to check once every hour, but you could just do it once on strartup.
So the Service is build like this:
public class UpdateService extends IntentService
{
//INtent Services have a constructer where you have to pass a String to the super class.
public UpdateService()
{
super("UpdateService");
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
super.onStartCommand(intent, flags, startId);
return Service.START_NOT_STICKY;
}
#Override
public void onDestroy()
{
super.onDestroy();
}
//This method downloads the file from a url and checks if its well formed. It has its own thread because android does not allow access to the internet in the same thread
private void checkfornews()
{
final SAXBuilder builder = new SAXBuilder();
new Thread(new Runnable()
{
public void run()
{
// command line should offer URIs or file names
try
{
Document doc = builder.build(url);
Element root = doc.getRootElement();
readnews(root);
// If there are no well-formedness errors,
// then no exception is thrown
}
// indicates a well-formedness error
catch (JDOMException e)
{
}
catch (IOException e)
{
}
}
}).start();
}
}
//checking for the lateste link, if its a new one we have a new link to show
private void readnews(Element root)
{
List<Element> children = root.getChildren();
Iterator<Element> childrenIterator = children.iterator();
while(childrenIterator.hasNext())
{
Element child = childrenIterator.next();
List<Element> grandchildren = child.getChildren();
Iterator<Element> grandchildrenIterator = grandchildren.iterator();
int counter=0;
while(grandchildrenIterator.hasNext())
{
Element grandchild = grandchildrenIterator.next();
String name = grandchild.getName();
if(name.equals("item") && counter ==0)
{
String title="";
String link="";
String category="";
List<Element> ggc = grandchild.getChildren();
Iterator<Element> ggci= ggc.iterator();
while(ggci.hasNext())
{
Element ggch = ggci.next();
if((ggch.getName()).equals("title"))
{
title=ggch.getText();
}
if(ggch.getName().equals("link"))
{
link=ggch.getText();
}
if(ggch.getName().equals("category"))
{
category=ggch.getText();
}
}
if(category.equals("SoundOfTheCity"))
{
counter++;
Logging.i(TAG, "found some news");
String latestnews = prefs.getString("SOTCNews", "");
if(!(latestnews.equals(link)))
{
Logging.i(TAG, "and its new");
Editor edit = prefs.edit();
edit.putString("SOTCNews", link);
edit.commit();
showNotification(title, link);
}
}
}
}
}
}
private void showNotification(String title, String link)
{
//do what needs to be done with the new data
}
#Override
protected void onHandleIntent(Intent intent)
{
checkfornews();
scheduleNext();
}
private void scheduleNext()
{
int checkingDelay = UPDATE_DELAY;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR, checkingDelay);
Intent intent = new Intent(this, UpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, UPDATEALARM, intent, 0);
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
}
}
depending on the form of the html file you have to redo the readnews method and then the showNotification method.
If you don't want to have a backend like wordpress (or use a blog-like feature) you could just store the content of a static webpage instead of the url and if this content changes, use the showNotification method. This would probably do the trick and is a lot less work.