We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Creating a Laravel Logger - Part 4: Creating our Android application

  • Neo Ighodaro
March 27th, 2019
For this part of the series, you will need Android Studio 3+ installed on your machine.

In this part, we will build an Android application for our logger. The Android app will display logs in a list and receive notifications for errors. We will combine the functionalities of Pusher Channels and Pusher Beams to achieve this.

In the previous parts of this series, we have been able to create the Laravel application that will push all the logs to Pusher. We also added the option to push the logs to Beams which will be triggered only when the log level is critical (error).

Here is how your app will look:

Let’s dig in!

Requirements

To follow along with this series you need the following things:

  • Completed previous parts of the series. Part 1, Part 2, Part 3
  • Laravel installed on your local machine. Installation guide.
  • Knowledge of PHP and the Laravel framework.
  • Composer installed on your local machine. Installation guide.
  • Android Studio >= 3.x installed on your machine (If you are building for Android).
  • Knowledge of Kotlin and the Android Studio IDE.
  • Xcode >= 10.x installed on your machine (If you are building for iOS).
  • Knowledge of the Swift programming language and the Xcode IDE.
  • A Pusher application. Create one here.
  • A Pusher Beams application. Create one here.

Creating the project

Open Android Studio and create a new application. Enter the name of your application, for example, AndroidLoggerClient and enter a corresponding package name. You can use com.example.androidloggerclient for your package name.

Make sure the Enable Kotlin Support check box is selected as this article is written in Kotlin. Next, select a suitable minimum SDK for your app, API 19 should be fine. Next, choose the Empty Activity template provided, stick with the MainActivity naming and click Finish. You may have to wait a while Gradle will prepare your project.

Completing Pusher Beams setup

Since Pusher Beams for Android relies on Firebase, we need an FCM key and a google-services.json file for our project. Go to your Firebase console and click the Add project card to initialize the app creation wizard.

Add the name of the project, read and accept the terms and conditions. After this, you will be directed to the project overview screen. Choose the Add Firebase to your Android app option. Enter the app’s package name - com.example.androidloggerclient (in our case), thereafter you download the google-services.json file. After downloading the file, skip the rest of the quick-start guide.

Add the downloaded file to the app folder of your project - AndroidLoggerClient/app/. To get the FCM key, go to your project settings on Firebase, under the Cloud Messaging tab, copy out the server key.

Open the Pusher Beams instance created earlier in the series, start the Android quick start and enter your FCM key. After adding it, select Continue and exit the guide.

Adding app dependencies

Here, we will add dependencies to be used for the application. First, open your project build.gradle file and add the google services classpath like so:

    // File: ./build.gradle
    // [...]

    dependencies {
      // other claspaths
      classpath 'com.google.gms:google-services:4.2.0'  
    }

    // [...]

Next, you open the main app build.gradle file and add the following:

    // File: ./app/build.gradle
    // [...]

    dependencies {
        // other dependencies
        implementation 'com.pusher:pusher-java-client:1.8.0'
        implementation 'com.android.support:recyclerview-v7:28.0.0'
        implementation 'com.android.support:cardview-v7:28.0.0'
        implementation 'com.google.firebase:firebase-messaging:17.3.4'
        implementation 'com.pusher:push-notifications-android:0.10.0'

    }
    apply plugin: 'com.google.gms.google-services'

    // [...]

This snippet adds Pusher’s dependencies for the app. We equally have some dependencies from the Android support library to help us in building our UIs. Next, sync your Gradle files.

Implementing realtime logs

We will now implement realtime logs for the app. These logs will be displayed on a list, so let’s start by setting up our list. Open the activity_main.xml file and replace it with this:

    <!-- File: ./app/src/main/res/layout/activity_main.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.androidloggerclient.MainActivity">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </android.support.constraint.ConstraintLayout>

This file represents the main screen of the app. Here we added a recyclerview, which represents the UI element for lists. We will configure it as we proceed. The next thing we will do is design how each item will look like. Create a new layout file log_list_row.xml and paste this:

    <!-- File: ./app/src/main/res/layout/log_list_row.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        app:cardCornerRadius="5dp"
        android:layout_margin="10dp"
        android:layout_height="wrap_content">

        <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_margin="10dp"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/logMessage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Hello Logger!"
                />

            <TextView
                android:id="@+id/logLevel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintTop_toBottomOf="@id/logMessage"
                tools:text="Warning"
                android:textSize="12sp"
                />

        </android.support.constraint.ConstraintLayout>

    </android.support.v7.widget.CardView>

This layout contains a cardview that wraps two texts. One text is for the log message and the other for the log level. We will now create a corresponding data model class which will hold two strings.

Create a new class named LogModel and paste this:

    // File: ./app/src/main/java/com/example/androidloggerclient/LogModel.kt
    data class LogModel(val logMessage:String , val logLevel:String)

Next, we need a class to manage items in the list, also called an adapter. Create a new class named LoggerAdapter and paste this:

    // File: ./app/src/main/java/com/example/androidloggerclient/LoggerAdapter.kt
    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView

    class LoggerAdapter : RecyclerView.Adapter<LoggerAdapter.ViewHolder>() {

        private var logList  = ArrayList<LogModel>()

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(LayoutInflater.from(parent.context)
                    .inflate(R.layout.log_list_row, parent, false))
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) =
                holder.bind(logList[position])

        override fun getItemCount(): Int = logList.size

        fun addItem(model: LogModel) {
            this.logList.add(model)
            notifyDataSetChanged()
        }

        inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

            private val logMessage = itemView.findViewById<TextView>(R.id.logMessage)!!
            private val logLevel = itemView.findViewById<TextView>(R.id.logLevel)!!

            fun bind(item: LogModel) = with(itemView) {

                logMessage.text = item.logMessage
                logLevel.text = item.logLevel

                when {
                    item.logLevel.toLowerCase() == "warning" -> {
                        logLevel.setTextColor(ContextCompat.getColor(context, R.color.yellow))
                    }
                    item.logLevel.toLowerCase() == "error" -> {
                        logLevel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_dark))
                    }
                    item.logLevel.toLowerCase() == "info" -> {
                        logLevel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light))

                    }
    }

            }

        }

    }

The adapter manages the list through its implemented methods marked with override . The onCreateViewHolder method uses our log_list_row layout to inflate each row of the list using a custom ViewHolder class created at the bottom of the snippet. The onBindViewHolder binds data to each item on the list, the getItemCount method returns the size of the list. The addItem method adds data to the list and refreshes it.

Also, in the above snippet, we add color to log level text based on the type of log. We imported the yellow into our colors.xml file, so add the color in your colors.xml file like so:

    <!-- File: ./app/src/main/res/values/colors.xml -->
    <color name="yellow">#FFFF00</color>

To finish the first part of our implementation, open your MainActivity.Kt file and do the following:

Add the following imports:

    // File: ./app/src/main/java/com/example/androidloggerclient/MainActivity.kt
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.support.v7.widget.LinearLayoutManager
    import com.pusher.client.Pusher
    import com.pusher.client.PusherOptions
    import kotlinx.android.synthetic.main.activity_main.*
    import org.json.JSONObject

This imports external classes we will make use of. Then you initialize the adapter in the class like so:

    // File: ./app/src/main/java/com/example/androidloggerclient/MainActivity.kt
    // [...]

    class MainActivity : AppCompatActivity() {

      private val mAdapter = LoggerAdapter()

      // [...]

    }

Next, you replace the onCreate method with this:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupRecyclerView()
        setupPusher()
    }

This method is one of the lifecycle methods in Android. Here, we called two other methods to help set up the recyclerview and Pusher. Add the methods like so:

    private fun setupRecyclerView() {
        with(recyclerView){
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = mAdapter
        }
    }

This assigns a layout manager and our initialized adapter instance to the recyclerview.

    private fun setupPusher() {
        val options = PusherOptions()
        options.setCluster("PUSHER_CLUSTER")
        val pusher = Pusher("PUSHER_API_KEY", options)

        val channel = pusher.subscribe("log-channel")

        channel.bind("log-event") { channelName, eventName, data ->
            println(data)
            val jsonObject = JSONObject(data)
            val model = LogModel(jsonObject.getString("message"), jsonObject.getString("loglevel"))
            runOnUiThread {
                mAdapter.addItem(model)
            }
        }

        pusher.connect()
    }

This sets up Pusher to receive logs from a Pusher channel.

Replace the Pusher placeholders with your own keys from your dashboard.

Finally, add the internet permission to the AndroidManifest.xml file like so:

    <!-- File: ./app/src/main/AndroidManifest.xml -->
    <uses-permission android:name="android.permission.INTERNET"/>

With this, whenever we receive a log, it is added to the list through the adapter. With this, the app can display logs as soon as events come in. Now let us go a step further to show notifications when the log is an error log.

Implementing realtime notifications

First, we will create an Android service to listen if we receive any notification and display it accordingly.

Create a new file named NotificationsMessagingService and paste this:

    // File: ./app/src/main/java/com/example/androidloggerclient/NotificationsMessagingService.kt
    import android.app.NotificationChannel
    import android.app.NotificationManager
    import android.app.PendingIntent
    import android.content.Intent
    import android.os.Build
    import android.support.v4.app.NotificationCompat
    import android.support.v4.app.NotificationManagerCompat
    import com.google.firebase.messaging.RemoteMessage
    import com.pusher.pushnotifications.fcm.MessagingService

    class NotificationsMessagingService : MessagingService() {

        override fun onMessageReceived(remoteMessage: RemoteMessage) {
            val notificationId = 10
            val channelId  = "logs"
            lateinit var channel:NotificationChannel
            val intent = Intent(this, MainActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
            val mBuilder = NotificationCompat.Builder(this, channelId)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentTitle(remoteMessage.notification!!.title!!)
                    .setContentText(remoteMessage.notification!!.body!!)
                    .setContentIntent(pendingIntent)
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true)

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val notificationManager = applicationContext.getSystemService(NotificationManager::class.java)
                val name = getString(R.string.channel_name)
                val description = getString(R.string.channel_description)
                val importance = NotificationManager.IMPORTANCE_DEFAULT
                channel = NotificationChannel("log-channel", name, importance)
                channel.description = description
                notificationManager!!.createNotificationChannel(channel)
                notificationManager.notify(notificationId, mBuilder.build())

            } else {
                val notificationManager =  NotificationManagerCompat.from(this)
                notificationManager.notify(notificationId, mBuilder.build())
            }

        }

    }

The onMessageReceived method in this service is alerted when a notification comes in. When the notification comes in, we display it to the user. Next, we need to register the notification service in the AndroidManifest.xml file. You can do it by adding this to your file:

    <!-- File: ./app/src/main/AndroidManifest.xml -->
    <application
        >
        <!-- [...] -->    
        <service android:name=".NotificationsMessagingService">
            <intent-filter android:priority="1">
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

    </application>

Next, let us setup Pusher beams in the MainActivity file. Create a method like so:

    private fun setupPusherBeams(){
        PushNotifications.start(applicationContext, "PUSHER_BEAMS_INSTANCE_ID")
        PushNotifications.subscribe("log-intrest")
    }

Replace the placeholder above with the actual credentials from your dashboard.

This initializes Pusher beams and subscribes to the error-logs interest. Next, add the method call to your onCreate method in the MainActivity class:

    override fun onCreate(savedInstanceState: Bundle?) {
        // [...]

        setupPusherBeams()
    }

If you now run your app, you should have something like this:

Conclusion

In this part, we have created the Android client for our logging monitoring. In the app, we display all logs being sent through the channels and the error logs are also sent as push notifications. In the next part of the series, we will create the iOS application for the log monitor.

The source code is available on GitHub.

Clone the project repository
  • Android
  • Beams
  • iOS
  • Kotlin
  • Laravel
  • PHP
  • Swift
  • Vue.js
  • Beams
  • Channels

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.