Build a simple social network with Kotlin
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.
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.
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.
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 usual127.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:
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:
Conclusion
In this article, we have used Pusher to quickly and easily add realtime updates to the social network app.
19 February 2018
by Christian Nwamba