Android Notification

Local Notification:

https://developer.xamarin.com/guides/android/application_fundamentals/notifications/local_notifications_in_android_walkthrough/#add-v4-support

 

            // Notification Example
            // When the user clicks the notification, SecondActivity will start up.
            Intent resultIntent = new Intent(this, typeof(AccountRegisterActivity));

            // Pass some values to SecondActivity:
            //resultIntent.PutExtras(valuesForActivity);

            // Construct a back stack for cross-task navigation:
            Android.Support.V4.App.TaskStackBuilder stackBuilder = Android.Support.V4.App.TaskStackBuilder.Create(this);
            stackBuilder.AddParentStack(Java.Lang.Class.FromType(typeof(AccountRegisterActivity)));
            stackBuilder.AddNextIntent(resultIntent);

            // Create the PendingIntent with the back stack:            
            PendingIntent resultPendingIntent =
                stackBuilder.GetPendingIntent(0, (int)PendingIntentFlags.UpdateCurrent);

            // Build the notification:
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .SetAutoCancel(true)                    // Dismiss from the notif. area when clicked
                .SetContentIntent(resultPendingIntent)  // Start 2nd activity when the intent is clicked.
                .SetContentTitle("Button Clicked")      // Set its title
                                                        // .SetNumber(count)                       // Display the count in the Content Info
                .SetSmallIcon(Resource.Drawable.Icon)  // Display this icon
                .SetContentText(String.Format(
                    "The button has been clicked {0} times.", 3)); // The message to display.

            // Finally, publish the notification:
            var buttonClickNotificationId = 12342;
            NotificationManager notificationManager =
                (NotificationManager)GetSystemService(Context.NotificationService);
            notificationManager.Notify(buttonClickNotificationId, builder.Build());

 Remote Notification via Firebase Cloud Messaging:

https://developer.xamarin.com/guides/android/application_fundamentals/notifications/remote-notifications-with-fcm/

 Be sure to set permission 

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

https://github.com/firebase/quickstart-android/issues/41

..and use data (as in, don't send any body messages)

Application handles notification messages only if the app is in foreground but it handles data messages even if the application is in background or closed..

https://stackoverflow.com/questions/38406517/fcm-push-notification-not-working-when-app-is-closed-android

So, if your app is open, you can send FCM messages from the Firebase Console and your app will receive the message. HOWEVER, if your app is closed/killed, the FCM messages from Firebase Console will not be received.

HOWEVER, if you send data messages only (no body messages), then the data messages WILL BE RECEIVED even if the app is closed/killed.

..and don't forget to clean your build before deploying, or you'll get an error saying Firebase is not initialized while the app is running.

Add this line anywhere in your app (MainActivity.cs is a good place) to help you catch this error during deployment:

// Capture some Firebase compilation error at runtime
            // If error, just clean first before rebuilding
            // https://forums.xamarin.com/discussion/96263/default-firebaseapp-is-not-initialized-in-this-process
            var id = GetString(Resource.String.gcm_defaultSenderId);

 

To send data your own website/server to device via FCM:

https://firebase.google.com/docs/cloud-messaging/server?authuser=2

 Example data to send via PostmanAPI:

https://stackoverflow.com/questions/37358462/firebase-onmessagereceived-not-called-when-app-in-background

 With Vibration and Sound:

https://stackoverflow.com/questions/6616180/whats-wrong-with-my-code-notification-no-sound-no-vibrate

 https://stackoverflow.com/questions/13950338/how-to-make-an-android-device-vibrate

 

Xamarin.Native Tutorial: Consuming WebService: Android

Now, we're going to see how to consume a webservice into an Android app.

As you have seen during setup, we have created a function that consumes data from rabbit.miaw.xyz. To tell you the truth, that's pretty much it. All you have to do then is to simply call that function from within the Android app.

However, if you directly call that function, what would happen is that your screen will stutter and hang, simply because you're calling it from the UI thread. When you call a long running service/function from the UI thread, the UI thread have to wait to receive a response. And while waiting, it can't service the user's presses nor do any animations that require the UI thread to be available.

Alright, so now let's do it.

We're going to divide whatever we do by steps so we can see the reasonings for why something is there.

Step 1

Create layout activity_invoicesonline.axml

Create a layout named activity_invoicesonline inside Resources/Layout/ and replace the contents with this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/recyclerview_invoicesonline"
  android:scrollbars="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />

Create InvoicesOnlineActivity

Inside the Activities folder, create an Activity named InvoicesOnlineActivity and replace it with this.

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using InvoiceJe.Data;
using InvoiceJe.Droid.Extensions;
using Android.Support.V7.Widget;
using InvoiceJe.Droid.Adapters;
using System.Threading.Tasks;
using System.Collections.Generic;
using InvoiceJe.Models;

namespace InvoiceJe.Droid.Activities
{
    [Activity(Theme = "@style/MasterTheme")]
    public class InvoicesOnlineActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Create your application here

            // set layout
            SetContentView(Resource.Layout.activity_invoicesonline);

            // set toolbar

            // others
            var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());
            IEnumerable<Invoice> invoices = new List<Invoice>();

            var t = Task.Run( async () => {
                invoices = await repository.GetInvoicesFromWebserviceAsync();
            });
            t.Wait();

            RecyclerView recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview_invoicesonline);
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.SetLayoutManager(layoutManager);
            InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(invoices);
            //adapter.ItemClick += OnItemClick; // register onItemClick
            recyclerView.SetAdapter(adapter);

            // divider
            // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
            recyclerView.AddItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.Vertical));

        }
    }
}

 

(Realize that we purposely did not include a toolbar, this is simply for clarity. We'll add a full one later.)

(We're also reusing the InvoicesRecyclerViewAdapter that we made in previous tutorials)

Create a Button From MainActivity to InvoicesOnlineActivity

Now, we need to create a button for us to view InvoicesOnlineActivity.

Since this is mostly just an exercise, let's just change the floating action button to point towards InvoicesOnlineActivity temporarily.

So, open MainActivity.cs and change the FloatingActionButton click to point to InvoicesOnlineActivity instead.

// Setup FloatingActionButton click
            FloatingActionButton navigateToCreateInvoicesActivityFloatingActionButton = FindViewById<FloatingActionButton>(Resource.Id.floatingactionbutton_navigatetoinvoicescreateviewmodel);
            navigateToCreateInvoicesActivityFloatingActionButton.Click +=
                delegate
                {
                    //Intent intent = new Intent(this, typeof(InvoicesCreateActivity));
                    Intent intent = new Intent(this, typeof(InvoicesOnlineActivity));
                    StartActivity(intent);
                };

Now, save and deploy.

When you run the app and click on the FloatingActionButton, you will feel that the screen hangs for a while before moving to the InvoicesOnlineScreen.

This is thanks to this line:

var t = Task.Run( async () => {
                invoices = await repository.GetInvoicesFromWebserviceAsync();
            });
            t.Wait();

What happened here is that we tried getting the Invoices from the Webservice, however we needed to Wait() to get a result from there before we can populate the RecyclerView with Invoices and then finally returning the OnCreate() function.

Since OnCreate() does not return until we get the data from Webservice, we have to wait until we get the result before OnCreate() returns and Android can continue with its lifecycle.

We call this programming synchronously.

Why can't we simply use the await keyword? Why did we run the webservice call method inside Task.Run()? This is because if we change our functions to async, we need to change the return type of the function that we overrode to Task<Object>, which is not always possible in Android. Hence, wrapping it inside Task.Run() is the best way to handle it without changing the Android built-in methods to async.

Alright, so now let's introduce multi-threading to free our UI thread from having to wait for the Invoices from webservice.

Step 2

Now I said we're going to introduce multi-threading, but technically we are already using multi-threading by using Task.Run().

Task.Run() means that we start a new thread, and then run the function that we defined inside it.

However, we also did Task.Wait(). This means that the current thread (which is the UI thread) will wait until the function in Task.Run() returns. Hence, why our UI is stuck.

So, let's make another change.

Change:

// others
            var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());
            IEnumerable<Invoice> invoices = new List<Invoice>();

            var t = Task.Run( async () => {
                invoices = await repository.GetInvoicesFromWebserviceAsync();
            });
            t.Wait();

            RecyclerView recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview_invoicesonline);
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.SetLayoutManager(layoutManager);
            InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(invoices);
            //adapter.ItemClick += OnItemClick; // register onItemClick
            recyclerView.SetAdapter(adapter);

            // divider
            // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
            recyclerView.AddItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.Vertical));

to

            var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());

            var t = Task.Run( async () => {
                var theInvoices = await repository.GetInvoicesFromWebserviceAsync();
                return theInvoices;
            })
            .ContinueWith(async (resultOfPreviousTask) => 
            {
                var returnedInvoices = await resultOfPreviousTask;

                RecyclerView recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview_invoicesonline);
                LinearLayoutManager layoutManager = new LinearLayoutManager(this);
                recyclerView.SetLayoutManager(layoutManager);
                InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(returnedInvoices);
                //adapter.ItemClick += OnItemClick; // register onItemClick
                recyclerView.SetAdapter(adapter);

                // divider
                // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
                recyclerView.AddItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.Vertical));

            }, TaskScheduler.FromCurrentSynchronizationContext());

Here's what happened.

.ContinueWith() is how we define the function that should run after a Task.Run() is finished while not blocking any lines below.

By using .ContinueWith(), we won't block our UI thread. The OnCreate() will continue to run and return after we have invoked Task.Run(). The populating RecyclerView lines will only run after Task.Run() returns a value.

Also, note that we have put TaskScheduler.FromCurrentSynchronizationContext() as one of the arguments in ContinueWith(). This is to tell Android to run our ContinueWith() function inside the UI thread. We need to do this because we can only modify the Views inside our main UI thread, we cannot modify our Views from a different thread.

Now that we know all of this, try deploying and running it.

Now when you click on the FloatingActionButton, we are now immediately sent to the InvoicesOnlineActivity. Yay!

Wait, but why is the screen blank? Oh, now the invoices are showing.

This stutter/brief blank screen is simply because our App is still waiting to get the Invoices from the webservice. After, our app gets the data, THEN the recyclerview is populated.

So, how to do this? Yup, this is why we have a loading message. So let's do that next.

Step 3: Loading ProgressBar

So, let's introduce a loading circle animation while we're waiting to fetch the Invoice data from our webservice.

Now, open activity_invoicesonline.axml and replace the contents with this:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview_invoicesonline"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone" />

  <ProgressBar
      android:id="@+id/progressbar_invoicesonline"
      style="?android:progressBarStyle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center" />
  
</FrameLayout>

First, we set the initial value RecyclerView's visibility to gone. And yup, there's a built-in ProgressBar view inside Android, so let's use it. Yay!

Next, inside InvoicesOnlineActivity.cs, inside the .ContinueWith(), replace the contents with this:

var returnedInvoices = await resultOfPreviousTask;

                RecyclerView recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview_invoicesonline);
                LinearLayoutManager layoutManager = new LinearLayoutManager(this);
                recyclerView.SetLayoutManager(layoutManager);
                InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(returnedInvoices);
                //adapter.ItemClick += OnItemClick; // register onItemClick
                recyclerView.SetAdapter(adapter);

                // divider
                // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
                recyclerView.AddItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.Vertical));

                // hide progressbar and show recyclerview
                var progressBar = FindViewById<ProgressBar>(Resource.Id.progressbar_invoicesonline);
                progressBar.Visibility = Android.Views.ViewStates.Gone;
                recyclerView.Visibility = Android.Views.ViewStates.Visible;

So, simply said, once recyclerview is populated, hide the progressbar and show the RecyclerView.

Try running the app. Click on the FloatingActionButton again.

Alright, now we're talking. We immediately see the Loading circle circling around. And then suddenly the RecyclerView shows. Now this is okay already, but we can do better.

Step 4: Animation

Okay, to lessen the suddenness of our RecyclerView showing up and startling our poor users, let's introduce animations (yay!!) inside our app.

Inside the Resources folder, create a folder named anim.

Inside the Resources/anim folder, create an xml file named item_animation_fall_down.xml and replace the contents with this:

<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300">

  <translate
      android:fromYDelta="-20%"
      android:toYDelta="0"
      android:interpolator="@android:anim/decelerate_interpolator"
        />

  <alpha
      android:fromAlpha="0"
      android:toAlpha="1"
      android:interpolator="@android:anim/decelerate_interpolator"
        />

  <scale
      android:fromXScale="105%"
      android:fromYScale="105%"
      android:toXScale="100%"
      android:toYScale="100%"
      android:pivotX="50%"
      android:pivotY="50%"
      android:interpolator="@android:anim/decelerate_interpolator"
        />

</set>

Next, create a file named layout_animation_fall_down.xml and replace the contents with this:

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/item_animation_fall_down"
    android:delay="15%"
    android:animationOrder="normal"
    />

Alright, now we have our animation definitions ready.

(Wait wait wait, what are those things? Well, animations deserve a whole new post of itself, but you can read more from ProAndroidDev on Android animations)

Now, let's tell our RecyclerView to use that XML animation that we have defined just now.

So, open activity_invoicesonline.axml and replace the contents with this:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview_invoicesonline"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/layout_animation_fall_down"
    android:visibility="gone" />

  <ProgressBar
      android:id="@+id/progressbar_invoicesonline"
      style="?android:progressBarStyle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center" />
  
</FrameLayout>

Basically, we're only adding the android:layoutAnimation="@anim/layout_animation_fall_down" property to our RecyclerView.

Alright, now this is alright already if we immediately show our RecyclerView to the users. Unfortunately, we need to wait until the we finish fetching data from our webservice, THEN we can show the RecyclerView and execute its animations.

So, let's tell the RecyclerView to run its animation when we have finished fetching data, hid the progressbar and showing the RecyclerView.

Inside the ContinueWith(), replace its contents with this:

                var returnedInvoices = await resultOfPreviousTask;

                RecyclerView recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview_invoicesonline);
                LinearLayoutManager layoutManager = new LinearLayoutManager(this);
                recyclerView.SetLayoutManager(layoutManager);
                InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(returnedInvoices);
                //adapter.ItemClick += OnItemClick; // register onItemClick
                recyclerView.SetAdapter(adapter);

                // divider
                // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
                recyclerView.AddItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.Vertical));

                // hide progressbar and show recyclerview
                var progressBar = FindViewById<ProgressBar>(Resource.Id.progressbar_invoicesonline);
                progressBar.Visibility = Android.Views.ViewStates.Gone;
                recyclerView.Visibility = Android.Views.ViewStates.Visible;

                recyclerView.ScheduleLayoutAnimation(); // animation

Basically, the line recyclerView.ScheduleLayoutAnimation(); does the trick. The method simply tells Android to run the animation of the RecyclerView again.

Now, try deploying and running the app again.

Woohoo! Now we're talking. Everything is smooth as butter.

Well, adding toolbars, CoordinatorLayouts and such is left as exercise to you ;)