Back to search

Build a simple social network with Kotlin

  • Christian Nwamba
February 19th, 2018
A basic understanding of Kotlin and Node.js is needed to follow this tutorial.

Social networks require live updates to your status stack. Take for instance, Facebook. If you are on your main profile page and you post a new status, it is reflected immediately on the page without need to refresh it.

It is engaging and saves users the extra muscle of having to click the refresh button. Taking this to a mobile context, if this feature is not present, we would have to restart the ativity or have to wait till the user closes the app and opens it again before he can see anything new. This is obviously a bad user experience.

What we will build

In this article, we will build a simple android app that shows our status as soon as it is posted.

social-network-kotlin-demo

Prerequisites

You need the following;

  • Knowledge of the Kotlin programming language
  • A Pusher application
  • Node JS - This is to enable us to setup a server.
  • Android Studio - Android studio 3.0.1 is recommended as it is the latest stable version at the time of this publication and it integrates Kotlin support with ease.

Pusher app setup

Head to the Pusher dashboard, create an account if you have none, login to your dashboard, and create a new Pusher app.

social-network-kotlin-create-app

Be careful to take not of the cluster used, eu in our case. Open your app details and note the keys in the App Keys tab. You will need these later in the article.

Server setup

We will build a Node.js server and run it locally. We first create a new folder and name it accordingly, say user-status-backend. cd to the folder, create a package.json file and paste this:

    {
      "name": "realtime-status-update",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "body-parser": "^1.18.2",
        "express": "^4.16.2",
        "pusher": "^1.5.1"
      }
    }

We also create an index.js file within the folder and paste this:

    // Load dependencies
    const express = require('express')
    const bodyParser = require('body-parser')
    const app = express()

    // App middlewares
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));

    // Initialize Pusher
    var Pusher = require('pusher');
    var pusher = new Pusher({
        appId: 'PUSHER_APP_ID',
        key: 'PUSHER_APP_KEY',
        secret: 'PUSHER_APP_SECRET',
        cluster: 'PUSHER_APP_CLUSTER',
        encrypted: true
    });

    // app's routes
    app.post('/update-status', (req, res) => {

        pusher.trigger('my-channel', 'my-event', {
            "message": req.query.status
        });

        res.json(req.query.status)

    })

    app.get('/', (req, res, next) => {
        res.json("Yeaaaa!!!!")
    })

    app.listen(3000, () => console.log('Running application...'))

Our server has one major endpoint, update-status which accepts an HTTP POST method with the message to be posted sent as one of the parameters.

Use your app keys in this file.

We then install the Node and Pusher modules in our folder directory using the following commands:

    npm install

Finally, we run the server:

    node index.js

With this, our server is up and running on port 3000, you can check it out on your browser first before forging ahead.

Building our realtime Kotlin application

Setting up the project

Open android studio and create a new project. Details to be provided include: the application name and the domain name. Click the “include kotlin support” checkbox to enable Kotlin in the project.

Next up, you select the minimum SDK which is the least Android version our app will support. Choose Empty Activity when asked to add an activity.

social-network-kotlin-create-new-project

The next screen gives you a chance to customize the name of the activity. We will leave ours as the default MainActivity and click finish.

Adding dependencies

We need the support library dependencies, Pusher client dependency and Retrofit dependency. The first will give us extra features to access while using the Android SDK, Pusher will provide us with the much needed real-time feature and Retrofit will enable us make network requests to our server. These dependencies will be added in our app-module build.gradle:

    // pusher dependency
    compile 'com.pusher:pusher-java-client:1.5.0'

    // part of the support libraries
    implementation 'com.android.support:design:26.1.0'

    // retrofit dependency
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'

After adding the dependencies, we sync it so that it can be downloaded and made available for use in our app.

Setting up Retrofit

Retrofit is a type-safe HTTP client for Android and Java built by Square, Inc. It is used for making network requests. For us to use Retrofit, we need an interface to define our endpoints. Create a new Kotlin file named ApiInterface.kt and paste this:

    import retrofit2.Call
    import retrofit2.http.POST
    import retrofit2.http.Query

    interface ApiInterface {
        @POST("/update-status")
        fun updateStatus(@Query("status") status:String): Call<String>
    }

Thereafter, we need to provide a Retrofit object. We will do this by creating a class named RetrofitClient.kt and pasting this:

    import okhttp3.OkHttpClient
    import retrofit2.Retrofit
    import retrofit2.converter.scalars.ScalarsConverterFactory

    class RetrofitClient {
        fun getClient(): ApiInterface {
            val httpClient = OkHttpClient.Builder()
            val builder = Retrofit.Builder()
                    .baseUrl("http://10.0.2.2:3000/")
                    .addConverterFactory(ScalarsConverterFactory.create())

            val retrofit = builder
                    .client(httpClient.build())
                    .build()
            return retrofit.create(ApiInterface::class.java)
        }
    }

The getClient function gives us an instance of Retrofit. While declaring the Retrofit object, the base url for our network requests and the converter to be used are defined.

We are using 10.0.2.2 because this is how the Android emulator recognizes localhost as against the usual 127.0.0.1

We also add the internet permission in the AndroidManifest.xml file. This should be done under the <manifest> tag:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="YOUR_APP_PACKAGE_NAME">

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

    </manifest>    

Designing our layout

In this app, we will have just one screen. It will contain an EditText for our input, a Button to send the message inputted and trigger a request to the server, and finally a RecyclerView to display our status messages. This will be housed in the activity_main.xml layout file:

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_margin="16dp"
        android:layout_height="match_parent"
        tools:showIn="@layout/activity_main">

        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">

            <EditText
                android:paddingLeft="10dp"
                android:hint="Whats on your mind?"
                android:background="@drawable/background"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="100dp"
                android:id="@+id/newStatus" />

            <Button
                android:layout_gravity="end"
                android:id="@+id/buttonPost"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="post" />

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

        </LinearLayout>

    </ScrollView>

We added a seprate drawable as background for the EditText tag. This gives it a box like look. Create a new drawable resource named background.xml and paste this:

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:thickness="0dp"
        android:shape="rectangle">
        <solid android:color="#FFFFFF" />
        <stroke android:width="2dp"
            android:color="@color/colorAccent"/>

    </shape>

If you build the project right now our layout will look like this:

social-network-kotlin-app-ui

This takes the shape of a generic social media platform where status updates are made. Next up, we create an adapter to handle the display of status messages on a list. Create a new class StatusAdapter.kt and paste this:

    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView
    import java.util.*

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

        private var statusList = ArrayList<String>()
        private var reversedList = ArrayList<String>()

        fun addMessage(newMessage: String){
            statusList.add(newMessage)
            reversedList = statusList
            Collections.reverse(reversedList)
            notifyDataSetChanged()
        }

        override fun getItemCount(): Int {
            return statusList.size
        }

        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent!!.context).inflate(android.R.layout.simple_list_item_1,parent,false)
            return ViewHolder(view)
        }

        override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
            holder!!.statusText.text = reversedList[position]
        }

        inner class ViewHolder(itemView: View?): RecyclerView.ViewHolder(itemView) {
            var statusText: TextView = itemView!!.findViewById(android.R.id.text1)
        }

    }

The onCreateViewHolder function returns an instance of our ViewHolder coupled with the view that gives us the layout design for each list item. The addMessage function adds a new message to the list. Still in this function, we also assign list to reversedList and reverse reversedList so that we can have the most recent updates on top of the list. The reversedList is used based on position to display each item in the onBindViewHolder function.

In the MainActivity class, paste this:

    import android.app.Activity
    import android.os.Bundle
    import android.support.v7.app.AppCompatActivity
    import android.support.v7.widget.LinearLayoutManager
    import android.view.View
    import android.view.inputmethod.InputMethodManager
    import android.widget.Toast
    import com.pusher.client.Pusher
    import com.pusher.client.PusherOptions
    import kotlinx.android.synthetic.main.activity_main.*
    import org.json.JSONObject
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response

    class MainActivity : AppCompatActivity() {

        lateinit var pusher:Pusher

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

            // setup recycler view and adapter
            val adapter = StatusAdapter()
            recyclerView.layoutManager = LinearLayoutManager(this)
            recyclerView.adapter = adapter

            // setup pusher to receive status update
            val options = PusherOptions()
            options.setCluster(PUSHER_CLUSTER)
            pusher = Pusher(PUSHER_API_KEY, options)
            val channel = pusher.subscribe("my-channel")
            channel.bind("my-event") { channelName, eventName, data ->
                val jsonObject = JSONObject(data)
                runOnUiThread { adapter.addMessage(jsonObject.getString("message")) }
            }

            // post status to server
            buttonPost.setOnClickListener {

                if (newStatus.text.isNotEmpty())
                    RetrofitClient().getClient().updateStatus(newStatus.text.toString()).enqueue(object : Callback<String> {
                        override fun onResponse(call: Call<String>?, response: Response<String>?) {
                            newStatus.text.clear()
                            hideKeyboard()
                        }

                        override fun onFailure(call: Call<String>?, t: Throwable?) {
                            Toast.makeText(this@MainActivity,"Error occurred",Toast.LENGTH_SHORT).show()
                        }
                    })
            }

        }

        private fun hideKeyboard() {
            val imm = this.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
            var view = this.currentFocus
            if (view == null)
                view = View(this)
            imm.hideSoftInputFromWindow(view.windowToken, 0)
        }

        override fun onResume() {
            super.onResume()
            pusher.connect()
        }

        override fun onPause() {
            super.onPause()
            pusher.disconnect()
        }

    }

In this snippet, we initialized the recycler view together with its adapter, we initialized Pusher using our keys from our dashboard and subscribed to a channel so as to get realtime updates, then we created a listener for our button that posts a message to the server when clicked.

Finally, we connected and disconnected Pusher in the onResume and onPause functions.

With this, the application is ready! When we run it, we see results like this example:

social-network-kotlin-demo-2

Conclusion

In this article, we have used Pusher to quickly and easily add realtime updates to the social network app.

  • Channels

© 2018 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 28 Scrutton Street, London EC2A 4RP.