Build a location feed app for Android with Kotlin

Introduction

Introduction

Often times we like to track and visualize our applications in a central place. Feeds are great for this! In this tutorial, we'll build an Android app with an activity feed that allows users to broadcast their locations and share with all other connected users in realtime.

We’ll build the Android app to monitor the activities of a Node.js REST API. Every time the endpoint of the API is hit, Pusher will publish an event with some information (location shared by the user) to a channel. This event will be received in realtime, on all the connected Android devices.

Here’s the app in action:

kotlin-location-sharing-demo-login
kotlin-location-sharing-demo-friends

Prerequisites

This tutorial uses the following technologies

To follow along, you’ll need to sign up with Pusher and gain access to your dashboard to create a Pusher Channels project. You will also need to have Android Studio v3+ installed to build the client part of this application. To build our server side script, you’ll need to download and install Node if you don’t already have it installed.

Client side

Now that you have that sorted out, let’s start building our Android app. Launch Android Studio and create a new project. Be sure to include Kotlin support. Enter an application name, in our case - Pusher-Location-Feeds

kotlin-location-sharing-create-android

Select application’s target SDK:

kotlin-location-sharing-target-devices

Choose the basic activity template:

kotlin-location-sharing-basic-activity

When the project build is completed, open your app level build.gradle file and update the dependencies like so:

1implementation fileTree(include: ['*.jar'], dir: 'libs')
2    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
3    implementation 'com.android.support:appcompat-v7:26.1.0'
4    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
5    implementation 'com.pusher:pusher-java-client:1.5.0'
6    implementation 'com.google.android.gms:play-services-maps:15.0.0'
7    implementation 'com.google.android.gms:play-services-location:15.0.0'
8
9    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
10    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
11    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
12    implementation 'com.android.support:recyclerview-v7:26.1.0'
13    implementation 'com.android.support:design:26.1.0'
14    testImplementation 'junit:junit:4.12'
15    androidTestImplementation 'com.android.support.test:runner:1.0.1'
16    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

Next, sync the project by clicking Sync Now with the gradle file to install the added dependencies.

Application activities

Login activity

By default creating the Android project also creates a MainActivity class and an associating activity_main.xml file for you. Now we need a login Activity to collect the users username. So create a new activity, right-click on MainActivity >> New >> Activity >> Empty Activity, then name it LoginActivity. Once this activity is created, it’ll create a default layout file activity_login.xml inside the layout folder under res. The layout will be a rather simple one, it will have a text input to collect the user’s username and a button to share their location. Here’s a snippet for the activity_login.xml file:

1<?xml version="1.0" encoding="utf-8"?>
2    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3        xmlns:app="http://schemas.android.com/apk/res-auto"
4        xmlns:tools="http://schemas.android.com/tools"
5        android:layout_width="match_parent"
6        android:layout_height="match_parent"
7        android:orientation="vertical"
8        android:gravity="center"
9        android:layout_margin="16dp"
10        tools:context=".LoginActivity">
11
12        <EditText
13            android:paddingStart="10dp"
14            android:id="@+id/userName"
15            android:layout_width="match_parent"
16            android:layout_height="50dp"
17            android:hint="Username"
18            android:background="@drawable/input_bg"
19            android:layout_marginEnd="5dp"/>
20
21        <Button
22            android:id="@+id/enterButton"
23            android:layout_marginTop="15dp"
24            android:layout_width="wrap_content"
25            android:padding="10dp"
26            android:text="Share Location"
27            android:textColor="@android:color/white"
28            android:background="@drawable/button"
29            android:layout_gravity="center"
30            android:layout_height="wrap_content" />
31
32    </LinearLayout>

Here we have a simple LinearLayout with two view objects, an EditText input to collect the user’s username and a share button to send the location to the server.

Android default styling isn’t always appealing so let’s add some custom styles to our layout simply for aesthetic purposes. Under res folder, open the values folder and navigate into colors.xml file and update it with this code :

1<?xml version="1.0" encoding="utf-8"?>
2    <resources>
3        <color name="colorPrimary">#8e0517</color>
4        <color name="colorPrimaryDark">#4c060f</color>
5        <color name="colorAccent">#FF4081</color>
6
7    </resources>

Secondly to achieve the button and Input styles, we create two drawable files. Under res right-click on drawable >>New >> Drawable resource file, name it input_bg and update it with this code:

1<shape xmlns:android="http://schemas.android.com/apk/res/android"
2        android:shape="rectangle" android:padding="10dp" >
3        <solid android:color="#FFFFFF"
4            />
5        <corners
6            android:radius="10dp"/>
7    </shape>

This simply adds round edges to the EditText object. For the button styles, follow the same steps as the one above and create a new drawable file, name it button and set it up like so:

1<shape xmlns:android="http://schemas.android.com/apk/res/android"
2        android:shape="rectangle" android:padding="10dp" >
3        <solid android:color="#4c060f" />
4        <corners
5            android:radius="10dp"/>
6    </shape>

Finally update your styles.xml file inside the values folder in the layout directory:

1<resources>
2        <!-- Base application theme. -->
3        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
4            <!-- Customize your theme here. -->
5            <item name="colorPrimary">@color/colorPrimary</item>
6            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
7            <item name="colorAccent">@color/colorAccent</item>
8        </style>
9        <style name="AppTheme.NoActionBar">
10            <item name="windowActionBar">false</item>
11            <item name="windowNoTitle">true</item>
12        </style>
13
14     <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
15
16    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
17    </resources>

At this point, your output in the xml visualizer should look exactly like this:

kotlin-location-sharing-part-1

Next lets create a new layout file called custom_view.xml. We’ll use this file to render each individual map of a user on our recyclerview object. Inside the layout folder under res, create the new layout resource file and set it up like so:

1<?xml version="1.0" encoding="utf-8"?>
2    <LinearLayout
3    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
4    android:layout_height="wrap_content">
5    <com.google.android.gms.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
6    android:id="@+id/map"
7    android:layout_margin="10dp"
8    android:name="com.google.android.gms.maps.SupportMapFragment"
9    android:layout_width="match_parent"
10    android:layout_height="200dp" />
11
12    </LinearLayout>

Okay, we are done with login and UI lets hook it up with it’s Java file to handle the logic. Open LoginActivity.kt file and set it up like so:

1//package your_project_package_here
2
3    import android.content.Intent
4    import android.os.Bundle
5    import android.support.v7.app.AppCompatActivity
6    import kotlinx.android.synthetic.main.activity_login.*
7
8    class LoginActivity : AppCompatActivity() {
9
10        override fun onCreate(savedInstanceState: Bundle?) {
11            super.onCreate(savedInstanceState)
12            setContentView(R.layout.activity_login)
13            enterButton.setOnClickListener {
14                if (userName.text.isNotEmpty()){
15                    val intent = Intent(this@LoginActivity,MainActivity::class.java)
16                    intent.putExtra("username",userName.text.toString())
17                    startActivity(intent)
18                }
19            }
20        }
21    }

Here we are simply getting the value of the input we defined in the layout file and passing it into the MainActivity class with an intent . Once the user has entered a value (username) in the Edittext object, we set a listener on the button to call the intent action when clicked. This action will only execute if the input value is not empty.

MainActivity

Next we define a layout where we’ll render the map locations of each user when they share their location. We’ll get their latitude and longitude coordinates along with the username they provided in the LoginActivity and send it to our server, which then returns a map of the location with the provided username on the map-marker and display it on screen for all users.

Before we get into MainActivity, let’s first define a new layout file with a RecyclerView object to hold these location widgets as the users share them. Under res, right-click on layout >> New >> Layout resource file and name it content_main, (if you selected the basic activity template while setting up the project, then you should have this file by default). Open this file and set it up like so:

1<?xml version="1.0" encoding="utf-8"?>
2    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
3        xmlns:app="http://schemas.android.com/apk/res-auto"
4        xmlns:tools="http://schemas.android.com/tools"
5        android:layout_width="match_parent"
6        android:layout_height="match_parent"
7        android:id="@+id/recyclerView"
8        android:layout_margin="16dp"
9        tools:showIn="@layout/activity_main"/>

As seen, we simply have a RecyclerView object where we’ll render each individual user's location so they can all appear in a list. Lastly, Open up activity_main.xml and update it:

1<?xml version="1.0" encoding="utf-8"?>
2    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3        xmlns:app="http://schemas.android.com/apk/res-auto"
4        xmlns:tools="http://schemas.android.com/tools"
5        android:layout_width="match_parent"
6        android:layout_height="match_parent"
7        tools:context=".MainActivity">
8
9        <android.support.design.widget.AppBarLayout
10            android:layout_width="match_parent"
11            android:layout_height="wrap_content"
12            android:theme="@style/AppTheme.AppBarOverlay">
13
14            <android.support.v7.widget.Toolbar
15                android:id="@+id/toolbar"
16                android:layout_width="match_parent"
17                android:layout_height="?attr/actionBarSize"
18                android:background="?attr/colorPrimary"
19                app:popupTheme="@style/AppTheme.PopupOverlay" />
20
21        </android.support.design.widget.AppBarLayout>
22
23        <include layout="@layout/content_main" />
24
25        <android.support.design.widget.FloatingActionButton
26            android:id="@+id/fab"
27            android:layout_width="wrap_content"
28            android:layout_height="wrap_content"
29            android:layout_gravity="bottom|end"
30            android:layout_margin="16dp"
31            app:srcCompat="@android:drawable/ic_menu_send" />
32
33    </android.support.design.widget.CoordinatorLayout>

Application logic

Since we used a RecyclerView in our layout file, we’ll need an adapter class. RecyclerView works with an Adapter to manage the items of its data source and a ViewHolder to hold a view representing a single list item. Before we create the Adapter class, lets first create a Model class that will interface between our remote data and the adapter. It’ll have the values that we’ll pass data to our recyclerview. Now right-click on MainActivity >> New >> Kotlin File/Class, name it Model, under the Kind dropdown, select Class and set it up like so:

1// java/package/Model
2
3    data class Model(val latitude:Double,
4                     val longitude:Double,
5                     val username:String)

Now that we have that, lets create the Adapter class. Right-click on MainActivity >> New >> Kotlin File/Class, name it Adapter, under the Kind dropdown, select Class again and set it up with the code:

1// java/package/Adapter
2    //package your_project_package_here
3
4    // imports  
5    import android.content.Context
6    import android.support.v7.widget.RecyclerView
7    import android.view.LayoutInflater
8    import android.view.View
9    import android.view.ViewGroup
10    import android.widget.TextView
11
12    class Adapter(private val mContext: AppCompatActivity)
13        : RecyclerView.Adapter<Adapter.MyViewHolder>() {
14
15        private var arrayList: ArrayList<Model> = ArrayList()
16        override fun getItemCount(): Int {
17            return arrayList.size
18        }
19        override fun onBindViewHolder(holder: Adapter.MyViewHolder, position: Int) {
20          val latLng = LatLng(arrayList[position].latitude,arrayList[position].longitude)
21            holder.mapView.onCreate(null)
22            holder.mapView.getMapAsync(OnMapReadyCallback {
23                it.addMarker(MarkerOptions()
24                        .title(arrayList[position].username)
25                        .position(latLng))
26            val cameraPosition = CameraPosition.Builder().target(latLng).zoom(17f).build()
27                it.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
28            })
29        }
30         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
31            val view = LayoutInflater.from(mContext).inflate(R.layout.custom_view, parent, false)
32            return MyViewHolder(view)
33        }
34
35        inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
36            val mapView:MapView = itemView.findViewById(R.id.map)
37        }
38
39        fun addItem(model: Model) {
40            this.arrayList.add(model)
41            notifyDataSetChanged()
42        }
43    }

Here we have defined an arrayList from our Model class that will be used by the adapter to populate the R``ecycler``V``iew. In the onBindViewHolder() method, we bind the locations coming from our server (as longitude and latitude) to the view holder we defined for it. We also passed the user’s username to the map marker.

Then in the onCreateViewHolder() method we define the design of the layout for individual items on the list. Finally the addItem() method adds a new instance of our model class to the arrayList and refresh the list every time we get a new addition.

Next let’s establish a connection to our Node server using the Retrofit library we installed at the beginning. First we create a new Kotlin interface to define the API endpoint we’ll be calling for this project. Right-click on MainActivity >> New >> Kotlin File/Class, under the Kind dropdown, select Interface name it Service and set it up like so:

1// imports ...
2    import retrofit2.Call
3    import retrofit2.http.GET
4    import retrofit2.http.Path
5
6    interface Service {
7        @POST("/location")
8        fun sendLocation(@Body coordinates: RequestBody): Call<String>
9    }

We also need a class that’ll give us an instance of Retrofit for making networking calls. It’ll also be the class where we’ll define the server URL and network parameters. So follow the previous steps and create a class called Client.kt and set it up like this:

1// imports...
2
3    import okhttp3.OkHttpClient
4    import retrofit2.Retrofit
5    import retrofit2.converter.gson.GsonConverterFactory
6    import retrofit2.converter.scalars.ScalarsConverterFactory
7
8    class Client {
9        fun getClient(): Service {
10            val httpClient = OkHttpClient.Builder()
11
12            val builder = Retrofit.Builder()
13                    .baseUrl("your_server_url")
14                    .addConverterFactory(ScalarsConverterFactory.create())
15                    .addConverterFactory(GsonConverterFactory.create())
16
17            val retrofit = builder
18                    .client(httpClient.build())
19                    .build()
20
21            return retrofit.create(Service::class.java)
22        }
23    }

Replace the Base URL with your localhost address for the Node server. We’ll

The baseUrl we used here points to our local Node server running on your machine as shown above but we’ll get to that later on in the tutorial. For now let’s go back to MainActivity.kt and initialize the necessary objects and update it with the classes we’ve created above.

1// imports ...
2
3    import android.Manifest
4    import android.annotation.SuppressLint
5    import android.content.DialogInterface
6    import android.content.pm.PackageManager
7    import android.location.Location
8    import android.os.Bundle
9    import android.support.design.widget.Snackbar
10    import android.support.v4.app.ActivityCompat
11    import android.support.v4.content.ContextCompat
12    import android.support.v7.app.AlertDialog
13    import android.support.v7.app.AppCompatActivity
14    import android.support.v7.widget.LinearLayoutManager
15    import android.util.Log
16    import com.google.android.gms.location.FusedLocationProviderClient
17    import com.google.android.gms.location.LocationServices
18    import com.google.android.gms.maps.model.LatLng
19    import com.google.android.gms.maps.model.MarkerOptions
20    import com.pusher.client.Pusher
21    import com.pusher.client.PusherOptions
22    import kotlinx.android.synthetic.main.activity_main.*
23    import kotlinx.android.synthetic.main.content_main.*
24    import okhttp3.MediaType
25    import okhttp3.RequestBody
26    import org.json.JSONObject
27    import retrofit2.Call
28    import retrofit2.Callback
29    import retrofit2.Response
30
31    class MainActivity : AppCompatActivity() {
32
33        var adapter: Adapter = Adapter(this@MainActivity)
34        lateinit var pusher:Pusher
35        val MY_PERMISSIONS_REQUEST_LOCATION = 100
36        private lateinit var fusedLocationClient: FusedLocationProviderClient
37
38        override fun onCreate(savedInstanceState: Bundle?) {
39            super.onCreate(savedInstanceState)
40            setContentView(R.layout.activity_main)
41            setSupportActionBar(toolbar)
42
43            }
44    }

Here we’ve just initialized the objects we’ll need, our Adapter class, Pusher, location request and the fusedLocationClient.

In the onCreate() method we’ll setup our RecyclerView with the adapter. We’ll also call the setupPusher() method and the sendLocation() action with the floating action button:

1// java/package/MainActivity.onCreate()
2     setupPusher()
3    fab.setOnClickListener { view ->
4        if (checkLocationPermission())
5            sendLocation()
6    }
7    with(recyclerView){
8        layoutManager = LinearLayoutManager(this@MainActivity)
9        adapter = this@MainActivity.adapter
10    }
11    //... continue from next snippet

While adding this code to your onCreate() method, be careful not to miss the curly braces

So we called methods we haven’t defined yet, that’s no problem we’ll define the setupPusher() method later on in the tutorial but first off, let’s define and setup the sendLocation() method this time, outside the onCreate():

1//java/package/MainActivity
2
3    //... continue from the last snippet above, outside the onCreate() method
4    @SuppressLint("MissingPermission")
5    private fun sendLocation() {
6        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
7        fusedLocationClient.lastLocation
8                .addOnSuccessListener { location: Location? ->
9                    if (location!=null){
10                        Log.e("TAG","location is not null")
11                        val jsonObject = JSONObject()
12                        jsonObject.put("latitude",location.latitude)
13                        jsonObject.put("longitude",location.longitude)
14                        jsonObject.put("username",intent.extras.getString("username"))
15
16                        val body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString())
17                        Log.e("TAG",jsonObject.toString())
18                        Client().getClient().sendLocation(body).enqueue(object: Callback<String> {
19                            override fun onResponse(call: Call<String>, response: Response<String>) {}
20
21                            override fun onFailure(call: Call<String>?, t: Throwable) {
22                                Log.e("TAG",t.message)
23                            }
24
25                        })
26
27                    } else {
28                        Log.e("TAG","location is null")
29                    }
30                }
31
32    }
33    //MainActivity

With the fusedLocationClient object we initialized earlier, we are getting the user’s location. If we succeed in getting the location, we pass the the longitude and latitude along with the user’s username into our body object. We then use it to build our HTTP request with the jsonObjects as our request parameters.

We also called the checkLocationPermission() method in the onCreate() method however we haven’t defined it yet. Lets now create this method and set it up like so:

1private fun checkLocationPermission(): Boolean {
2        if (ContextCompat.checkSelfPermission(this,
3                        Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
4
5            // Should we show an explanation?
6            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
7                            Manifest.permission.ACCESS_FINE_LOCATION)) {
8
9                AlertDialog.Builder(this)
10                        .setTitle("Location permission")
11                        .setMessage("You need the location permission for some things to work")
12                        .setPositiveButton("OK", DialogInterface.OnClickListener { dialogInterface, i ->
13
14                            ActivityCompat.requestPermissions(this@MainActivity,
15                                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
16                                    MY_PERMISSIONS_REQUEST_LOCATION)
17                        })
18                        .create()
19                        .show()
20
21            } else {
22                // No explanation needed, we can request the permission.
23                ActivityCompat.requestPermissions(this,
24                        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
25                        MY_PERMISSIONS_REQUEST_LOCATION)
26            }
27            return false
28        } else {
29            return true
30        }
31    }

Of course we can’t just grab every user’s location without first asking for their permission, so here’s how we set up the method that requests permission to access their location. Just after the sendLocation() method, add:

1// MainActivity
2    override fun onRequestPermissionsResult(requestCode: Int,
3     permissions: Array<String>, grantResults: IntArray) {
4      when (requestCode) {
5            MY_PERMISSIONS_REQUEST_LOCATION -> {
6                // If request is cancelled, the result arrays are empty.
7       if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
8                    // permission was granted, yay! Do the
9                    // location-related task you need to do.
10                    if (ContextCompat.checkSelfPermission(this,
11        Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
12                     sendLocation()
13             }
14         } else {
15               // permission denied!
16            }
17              return
18          }
19      }
20    }
21    //MainActivity

And now let’s define the setUpPusher() method we called earlier in the onCreate() method:

1// MainActivity
2    private fun setupPusher() {
3        val options = PusherOptions()
4        options.setCluster("eu")
5        pusher = Pusher("9117088b176802bda36f", options)
6        val channel = pusher.subscribe("feed")
7        channel.bind("location") { _, _, data ->
8            val jsonObject = JSONObject(data)
9            Log.d("TAG",jsonObject.toString())
10            val lat:Double = jsonObject.getString("latitude").toDouble()
11            val lon:Double = jsonObject.getString("longitude").toDouble()
12            val name:String = jsonObject.getString("username").toString()
13            runOnUiThread {
14                val model = Model(lat,lon,name)
15                adapter.addItem(model)
16            }
17        }
18    }
19    //MainActivity

Here we simply pass in our Pusher configs to the Pusher object and subscribe to the feed channel to listen for location events. Then we get the data returned from the server into our defined variables and pass them to our model class to update the adapter.

Next we implement the onStart() and onStop() methods to connect and disconnect Pusher respectively in our app:

1override fun onStart() {
2        super.onStart()
3        pusher.connect()
4    }
5
6    override fun onStop() {
7        super.onStop()
8        pusher.disconnect()
9    }

Finally on the client side, we create a Kotlin data class that will define the payload we’ll be requesting from the server. Following the previous steps, create a class called RequestPayload and set it up like so:

1// java/package/RequestPayload
2
3    //package your_project_package_here
4
5    data class RequestPayload(var latitude:Double,var longitude:Double,var username:String)

Server side

Set up Pusher

Now that we have all the client side functionalities, lets go ahead and build our server. But first, if you haven’t, now will be a good time to create a free account here. When you first log in, you'll be asked to enter some configuration options:

kotlin-location-sharing-pusher-1

Enter a name, choose Android as your front-end tech, and Node.js as your back-end tech. This will give you some sample code to get you started along with your project api keys:

kotlin-location-sharing-pusher-2

Then go to the App Keys tab and copy your app_id, key, and secret credentials, we'll need them later.

Set up a Node server

For this we will use Node. So check that you have node and npm installed on your machine by running this command in command prompt:

1node --version
2    //should display version numbers
3
4    npm --version
5    //should display version numbers

If that is not the case, Download and Install Node.

Next lets start building our server side script. Still in command prompt, run:

1mkdir pusherLocationFeeds
2    //this creates a project directory to host your project files
3
4    cd pusherLocationFeeds
5    // this navigates into the just created directory
6
7    npm init -y
8    //this creates a default package.json file to host our project dependencies

Let’s install the Node modules we’ll need for this project. Basically we’ll need Express, Pusher and body-parser. Inside the project directory, run:

    npm install express, body-parser, pusher

You can always verify these installations by opening your package.json file, at this point the dependency block should look like this :

1"dependencies": {
2        "body-parser": "^1.18.2",
3        "express": "^4.16.3",
4        "pusher": "^1.5.1"
5      }

Next create a server.js file in the project directory. First we require the Node modules we installed:

1var express = require("express")
2    var pusher = require("pusher")
3    var bodyParser = require("body-parser")

Next we configure Express:

1var app = express();
2    app.use(bodyParser.json());
3    app.use(bodyParser.urlencoded({ extended: false }));

Lets now create the Pusher object by passing the configuration object with the id, key, and the secret for the app created in the Pusher Dashboard:

1var pusher = new Pusher({    
2        appId: "pusher_app_id",
3        key: "pusher_app_key",
4        secret: "pusher_app_secret",
5        cluster: "pusher_app_cluster"
6      });

As we described earlier, we’ll use Pusher to publish events that happen in our application. These events have an eventChannel, which allows them to relate to a particular topic, an eventName that is used to identify the type of the event, and a *payload*, which you can attach any additional information to and send back to the client.

In our case, we’ll publish an event to a Pusher channel (“feed”) when the endpoint of our API is called. Then send the information as an attachment so we can show it in an activity feed on the client side.

Here's how we define our API's REST endpoint:

1app.post('/location', (req, res,next)=>{
2
3        var longitude = req.body.longitude;
4        var latitude = req.body.latitude;
5        var username = req.body.username;
6      ...

Here when we receive request parameters, we’ll extract the longitude, latitude and the username of the sender from the request and send back as response to the client like so:

1...  
2        pusher.trigger('feed', 'location', {longitude, latitude,username});
3        res.json({success: 200});
4      });

Now when a user types in a username and clicks the share location button, the server returns the data like:

1{
2      "longitude" : "longitude_value"
3      "latitude" : "latitude_value"
4      "username" : "username_value"
5    }

From here, we then use the adapter to pass it to the V``iewHolder and lay it out on the screen. When you’re done, your server.js file should look like this:

1var pusher = require("pusher")
2    var express = require("express")
3    var Pusher = require("pusher")
4    var bodyParser = require("body-parser")
5    var pusher = new Pusher({
6        appId: "app_id",
7        key: "app_key",
8        secret: "app_secrete",
9        cluster: "app_cluster"
10      });
11    var app = express();
12    app.use(bodyParser.json());
13    app.use(bodyParser.urlencoded({ extended: false }));
14
15    app.post('/location', (req, res,next)=>{
16
17        var longitude = req.body.longitude;
18        var latitude = req.body.latitude;
19        var username = req.body.username;
20
21        pusher.trigger('feed', 'location', {longitude, latitude,username});
22        res.json({success: 200});
23    });
24    app.listen(4040, function () {
25        console.log('Listening on 4040')
26      })

Now navigate to the terminal and cd into the server.js file. Then run the server with:

    $ node server

Run app

Once the server is live, go ahead and run the Android app. To run the app, keep your system connected to the internet. Back in Android Studio, click the green play icon on the menu bar to run the application or select Run from the menu and click Run ‘app’ from the dropdown. This action will launch your device modal for you to see all connected devices and emulators. If you’re using a physical device, simply select your device from the list of available devices shown and click OK.

If you’re running on an emulator, select your preferred emulator from the list of devices if you have one setup or follow these instructions to set up a new emulator:

On the devices modal, select Create New Virtual Device. This will launch a hardware selection modal where you will select any device of your choice for instance ( Nexus 5) and click Next. This will launch another modal where you will select the API level you will like to run on the device. Your can choose any of the available options for you or stick with the default and select API level 25. Click Next again to give your emulator a custom name and then click Finish to complete the setup. Now when you run the app again, you will see your emulator listed on the available devices modal. With your system still connected to the internet, select your preferred device and click Ok to run.

Conclusion

Hopefully, this tutorial has shown you in an easy way, how to build an activity feed for Android apps with Pusher. As you continue to build stuff, Perhaps you’ll see for yourself that realtime updates are of great importance. When you do, Pusher has all you’ll need to get pushing. Project is available on Github and the server side code also available on this gist.