A stock market, equity market or share market is the aggregation of buyers and sellers (a loose network of economic transactions, not a physical facility or discrete entity) of stocks (also called shares), which represent ownership claims on businesses; these may include securities listed on a public stock exchange as well as those only traded privately. - Wikipedia
Building a stock market application has some fundamental requirements. Apart from accuracy, the application needs to also be able to update prices in realtime as the changes occur. It should also have an option to notify the user if there is a change in the price of a stock, even when they are not currently looking at the application.
In this post, we will see how we can achieve this using Kotlin, Pusher Beams, and Pusher Channels.
When we are done with the article, here is what our app will look like:
To follow along in this tutorial, you need the following:
The first thing we want to do is create our Android application. Open Android Studio and create a new application.
Enter the name of your application, StockExchangeApp
, and then enter the package name, which is com.example.stockexchangeapp
. Make sure the Enable Kotlin Support checkbox is selected, choose the minimum SDK, we chose API 19, click Next. Choose an Empty Activity template and click Finish.
For this article, you need a Pusher application. To get started, go to the Pusher dashboard and create a new Channels app. When you create a new app, you are provided with the keys. We will need them later. For more details, visit the Pusher Channels docs.
For Pusher Beams to work, we need an FCM key and a google-services.json
file. Go to your Firebase console and click the Add project card to initialize the app creation wizard. Add the name of 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.
The next screen will require the package name of your app. You can find your app’s package name is your app-module build.gradle
file. Look for the applicationId
value. When you enter the package name and click Next, you will be prompted to download a google-services.json
file. Download the file and skip the rest of the process. Add the downloaded file to the app folder of your project - StockExchangeApp/app
.
To get the FCM key, go to your project settings on Firebase, under the Cloud messaging tab, copy out the server key.
Go get additional details, visit Pusher Beams docs.
Now that we have set up our Firebase application, go to the Pusher dashboard and click on the CREATE button on the BEAMS card.
After creating the instance, you will be presented with a quickstart guide. Select the ANDROID quickstart.
The next screen requires the FCM key which you copied earlier. After you add the FCM key, you can exit the guide.
Since this application will depend on other libraries to function, let’s pull in these dependencies so they are available to the project.
Open the project build.gradle
file and add the add the following:
1// File: ./build.gradle 2 buildscript { 3 // [...] 4 5 dependencies { 6 // [...] 7 8 classpath 'com.google.gms:google-services:4.0.0' 9 } 10 }
And these other dependencies to the app-module build.gradle
file:
1// File: ./app/build.gradle 2 dependencies { 3 4 // [...] 5 6 // Support library 7 implementation 'com.android.support:appcompat-v7:28.0.0-rc01' 8 implementation 'com.android.support:recyclerview-v7:28.0.0-rc01' 9 implementation 'com.android.support:preference-v7:28.0.0-rc01' 10 11 // Pusher Channel 12 implementation 'com.pusher:pusher-java-client:1.8.0' 13 14 // Pusher Beams 15 implementation 'com.google.firebase:firebase-messaging:17.3.0' 16 implementation 'com.pusher:push-notifications-android:0.10.0' 17 } 18 19 // Add this line to the end of the file 20 apply plugin: 'com.google.gms.google-services'
So far, we have been setting up the project. Now let’s start building the application. Let’s start by tweaking the colors of the application.
Open the colors.xml
file and add the following code to it:
1<!-- File: ./app/src/main/res/values/colors.xml --> 2 <color name="colorPrimary">#9E9E9E</color> 3 <color name="colorPrimaryDark">#424242</color> 4 <color name="colorAccent">#607D8B</color>
Next, open your styles.xml
file and replace the parent theme on the app theme with this - Theme.AppCompat
.
Apart from the initial MainActivity
already created for us, we will need a screen to manage the settings for the application.
Create a new Empty Activity named SettingsActivity
. Open the layout created for it - activity_settings
and replace everything except the first line of the file with the following code:
1<!-- File: ./app/src/main/res/layout/activity_settings.xml --> 2 <FrameLayout 3 android:background="#000" 4 xmlns:android="http://schemas.android.com/apk/res/android" 5 xmlns:tools="http://schemas.android.com/tools" 6 android:layout_width="match_parent" 7 android:id="@+id/frame_layout" 8 android:layout_height="match_parent" 9 tools:context=".SettingsActivity"/>
Next, open the SettingsActivity
file and set it up like this:
1// File: ./app/src/main/java/com/example/stockexchangeapp/SettingsActivity.kt 2 // [...] 3 4 import android.os.Bundle 5 import android.support.v7.app.AppCompatActivity 6 7 class SettingsActivity: AppCompatActivity() { 8 9 override fun onCreate(savedInstanceState: Bundle?) { 10 super.onCreate(savedInstanceState) 11 setContentView(R.layout.activity_settings) 12 supportFragmentManager.beginTransaction() 13 .replace(R.id.frame_layout, SettingsFragment()) 14 .commit() 15 } 16 17 }
In the code above, we replaced the frame layout with a fragment. This is the recommended practice when creating a settings page. Before creating the fragment, let’s create a preference file.
Create a new file in the xml
directory named preference
and paste the following:
1<!-- File: ./app/src/main/res/xml/preference.xml --> 2 <PreferenceScreen 3 xmlns:android="http://schemas.android.com/apk/res/android"> 4 5 <CheckBoxPreference 6 android:key="amazon_preference" 7 android:title="Amazon" 8 android:summary="Receive stock updates for Amazon" 9 android:defaultValue="true" /> 10 11 <CheckBoxPreference 12 android:key="apple_preference" 13 android:title="Apple" 14 android:summary="Receive stock updates for Apple" 15 android:defaultValue="true" /> 16 17 </PreferenceScreen>
In this file, we have two checkboxes to control the updates for two stocks, Amazon and Apple.
Next, create a new class named SettingsFragment
and paste the following code:
1// File: ./app/src/main/java/com/example/stockexchangeapp/SettingsFragment.kt 2 // [...] 3 4 import android.os.Bundle 5 import android.support.v7.preference.PreferenceFragmentCompat 6 7 class SettingsFragment: PreferenceFragmentCompat() { 8 9 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 10 // Load the preferences from an XML resource 11 setPreferencesFromResource(R.xml.preference, rootKey) 12 } 13 14 }
The code above loads the settings from the preference file created earlier. With this, we are done implementing the settings screen.
The next screen to be added will be a list of stock prices, which will be shown in the MainActivity
. To do this, we need a list. Open the activity_main.xml
file and paste the following:
1<!-- File: ./app/src/main/res/layout/activity_main.xml --> 2 <android.support.constraint.ConstraintLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:app="http://schemas.android.com/apk/res-auto" 5 xmlns:tools="http://schemas.android.com/tools" 6 android:layout_width="match_parent" 7 android:layout_height="match_parent" 8 android:background="#000" 9 tools:context=".MainActivity"> 10 11 <android.support.v7.widget.RecyclerView 12 android:id="@+id/recyclerView" 13 android:layout_width="match_parent" 14 android:layout_height="match_parent"/> 15 16 </android.support.constraint.ConstraintLayout>
This layout has a ConstraintLayout
housing a RecyclerView
. Since we are using a list, we need some other utilities. One of those utilities is a data object. The object is what every item on the list will hold.
Related: Getting started with ConstraintLayout in Kotlin.
Create a new class named StockModel
and paste this:
1// File: ./app/src/main/java/com/example/stockexchangeapp/StockModel.kt 2 data class StockModel(var name: String, var currentValue: Double, var changeValue:Double)
A data class in Kotlin generates some other useful methods we would have had to create manually if we were using Java.
Next, let’s design a layout for how each list item will look. Create a new layout file named list_row
and paste this:
1<!-- File: ./app/src/main/res/layout/list_row.xml --> 2 <android.support.constraint.ConstraintLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:app="http://schemas.android.com/apk/res-auto" 5 xmlns:tools="http://schemas.android.com/tools" 6 android:layout_width="match_parent" 7 android:layout_height="wrap_content"> 8 9 <TextView 10 android:layout_margin="10dp" 11 android:id="@+id/stockName" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:layout_marginStart="20dp" 15 tools:text="Amazon" 16 android:textAppearance="@style/TextAppearance.AppCompat.Display1" 17 android:textSize="18sp" 18 app:layout_constraintStart_toStartOf="parent" 19 app:layout_constraintBottom_toBottomOf="parent" 20 app:layout_constraintTop_toTopOf="parent" /> 21 22 <TextView 23 android:id="@+id/changeValue" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 tools:text="+5%" 27 app:layout_constraintTop_toTopOf="parent" 28 android:layout_marginEnd="20dp" 29 android:paddingEnd="5dp" 30 android:paddingStart="5dp" 31 app:layout_constraintBottom_toBottomOf="parent" 32 app:layout_constraintEnd_toEndOf="parent" /> 33 34 <TextView 35 android:id="@+id/currentValue" 36 android:layout_width="wrap_content" 37 android:layout_height="wrap_content" 38 android:layout_marginEnd="10dp" 39 tools:text="1234.9" 40 app:layout_constraintEnd_toStartOf="@id/changeValue" 41 app:layout_constraintTop_toTopOf="parent" 42 app:layout_constraintBottom_toBottomOf="parent" /> 43 44 </android.support.constraint.ConstraintLayout>
From this layout, each list item will show a company name, the current stock price, and it’ll show the change percentage.
Next, let’s create the adapter for the list. Create a new class named StockListAdapter
and paste this:
1// File: ./app/src/main/java/com/example/stockexchangeapp/StockListAdapter.kt 2 // [...] 3 4 import android.support.v4.content.res.ResourcesCompat 5 import android.support.v7.widget.RecyclerView 6 import android.view.LayoutInflater 7 import android.view.View 8 import android.view.ViewGroup 9 import android.widget.TextView 10 11 class StockListAdapter(private val stockList:ArrayList<StockModel>) : RecyclerView.Adapter<StockListAdapter.ViewHolder>() { 12 13 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 14 return ViewHolder(LayoutInflater.from(parent.context) 15 .inflate(R.layout.list_row, parent, false)) 16 } 17 18 override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(stockList[position]) 19 20 override fun getItemCount(): Int = stockList.size 21 22 fun addItem(item:StockModel){ 23 stockList.add(item) 24 notifyDataSetChanged() 25 } 26 27 fun updateItem(item:StockModel) { 28 stockList.forEachIndexed { index, element -> 29 if (element.name == item.name){ 30 stockList[index].changeValue = item.changeValue 31 stockList[index].currentValue = item.currentValue 32 notifyItemChanged(index) 33 } 34 } 35 } 36 37 fun contains(item: StockModel):Boolean{ 38 for (stock in stockList){ 39 if (stock.name==item.name){ 40 return true 41 } 42 } 43 44 return false 45 } 46 47 fun removeItem(name: String) { 48 val it = stockList.iterator() 49 50 while (it.hasNext()) { 51 val value = it.next() 52 if (value.name == name){ 53 it.remove() 54 } 55 } 56 57 notifyDataSetChanged() 58 } 59 60 inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 61 private val changePercent: TextView = itemView.findViewById(R.id.changeValue) 62 private val stockName: TextView = itemView.findViewById(R.id.stockName) 63 private val currentValue: TextView = itemView.findViewById(R.id.currentValue) 64 65 fun bind(item: StockModel) = with(itemView) { 66 stockName.text = item.name 67 currentValue.text = item.currentValue.toString() 68 val fmt = "%s%s" 69 70 changePercent.text = String.format(fmt, item.changeValue.toString(), "%") 71 72 if (item.changeValue.toString().contains("-")){ 73 changePercent.background = ResourcesCompat.getDrawable(resources, android.R.color.holo_red_dark, null) 74 } else { 75 changePercent.background = ResourcesCompat.getDrawable(resources, android.R.color.holo_green_dark, null) 76 } 77 } 78 } 79 }
This class manages the display of stock items on the list. It collects an initial list passed from the constructor and uses the size of that to know how many items we have.
The list can be updated with the additem
, updateItem
, and removeItem
methods we created. The list_row
layout we designed earlier is used in the onCreateViewHolder
method. In the bind
method of the ViewHolder
class, apart from adding the values to the necessary text views, we apply a green or red background to the changePercent
text view if it is a positive or negative value.
For uniformity, we will create a new class that will hold the list items we will use throughout the client app. Create a new class named MyStockList
and paste this:
1// File: app/src/main/java/com/example/stockexchangeapp/MyStockList.kt 2 // [...] 3 4 class MyStockList{ 5 companion object { 6 val stockList = ArrayList<StockModel>() 7 init { 8 stockList.add(StockModel("Apple", 0.0, 0.0)) 9 stockList.add(StockModel("Amazon", 0.0, 0.0)) 10 } 11 } 12 }
For this demo, we are considering two stocks only. You can add more if you like. These stocks have a default value of 0.0 for change percent and value.
Next, we will add some logic to our MainActivity
file. Open the file and paste the following:
1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 // [...] 3 4 import android.content.Intent 5 import android.content.SharedPreferences 6 import android.os.Bundle 7 import android.preference.PreferenceManager 8 import android.support.v7.app.AppCompatActivity 9 import android.support.v7.widget.DividerItemDecoration 10 import android.support.v7.widget.LinearLayoutManager 11 import android.util.Log 12 import android.view.Menu 13 import android.view.MenuItem 14 import com.pusher.client.Pusher 15 import com.pusher.client.PusherOptions 16 import com.pusher.pushnotifications.PushNotifications 17 import kotlinx.android.synthetic.main.activity_main.* 18 import org.json.JSONObject 19 20 class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener { 21 22 private val mAdapter = StockListAdapter(ArrayList()) 23 private lateinit var sharedPreferences: SharedPreferences 24 private val options = PusherOptions().setCluster("PUSHER_CLUSTER") 25 private val pusher = Pusher("PUSHER_KEY", options) 26 private val channel = pusher.subscribe("stock-channel") 27 28 override fun onCreate(savedInstanceState: Bundle?) { 29 super.onCreate(savedInstanceState) 30 setContentView(R.layout.activity_main) 31 setupPrefs() 32 pusher.connect() 33 setupRecyclerView() 34 setupPusherChannels() 35 setupPushNotifications() 36 } 37 38 }
This class implements the SharedPreferences.OnSharedPreferenceChangeListener
interface because we will add settings functionality in the app and the callback will tell us when the settings have been updated.
We created instance variables for our Pusher Channel object and the list adapter. We subscribed to the stock-channel
channel to listen for stock updates.
NOTE: Replace the Pusher holders with the keys on your Pusher Channel dashboard
Other methods called in the onCreate
method include:
setupPrefs()
- this method initializes the sharedPreferences
variable and initializes our settings with the default values. Add the method to the class:1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 private fun setupPrefs() { 3 PreferenceManager.setDefaultValues(this, R.xml.preference, false) 4 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) 5 }
setupRecyclerView()
- this method initializes our RecyclerView
. Add the method to the class:1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 private fun setupRecyclerView() { 3 with(recyclerView) { 4 layoutManager = LinearLayoutManager(this@MainActivity) 5 adapter = mAdapter 6 addItemDecoration( 7 DividerItemDecoration(recyclerView.context, DividerItemDecoration.VERTICAL) 8 ) 9 } 10 }
setupPusherChannels()
- this method loops through the stock list and looks for the stocks enabled in our settings page. If any of the stock is enabled, we subscribe to receive updates. Add the method to the class:1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 private fun setupPusherChannels(){ 3 val sharedPref = PreferenceManager.getDefaultSharedPreferences(this) 4 MyStockList.stockList.forEachIndexed { index, element -> 5 val refKey = element.name.toLowerCase() + "_preference" 6 val refValue = sharedPref.getBoolean(refKey, false) 7 if (refValue) { 8 if (!mAdapter.contains(element)) { 9 mAdapter.addItem(element) 10 channel.bind(element.name) { channelName, eventName, data -> 11 val jsonObject = JSONObject(data) 12 runOnUiThread { 13 mAdapter.updateItem( 14 StockModel( 15 eventName, 16 jsonObject.getDouble("currentValue"), 17 jsonObject.getDouble("changePercent") 18 ) 19 ) 20 } 21 } 22 } 23 } else { 24 mAdapter.removeItem(element.name) 25 channel.unbind(element.name){ _, _, _ -> } 26 } 27 } 28 }
setupPushNotifications()
- this method initializes Pusher Beams and listens to stock interests. Add the method to the class:1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 private fun setupPushNotifications() { 3 PushNotifications.start(applicationContext, "PUSHER_BEAMS_INSTANCEID") 4 PushNotifications.subscribe("stocks") 5 }
NOTE: Replace the
PUSHER_BEAMS_INSTANCEID
placeholder with the Pusher Beams instance ID on your dashboard.
Remember we created a settings page earlier? Let’s inflate our menu and link it to the settings page. To do this, first, let’s first create a menu file in the menu resource folder.
Create a new menu file named menu_main.xml
and paste this:
1<!-- File: app/src/main/res/menu/menu_main.xml --> 2 <menu xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto"> 4 5 <item 6 android:id="@+id/settings" 7 app:showAsAction="collapseActionView" 8 android:title="@string/settings" /> 9 10 </menu>
Now, add these methods to your MainActivity
file:
1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 override fun onCreateOptionsMenu(menu: Menu): Boolean { 3 menuInflater.inflate(R.menu.menu_main, menu) 4 return true 5 } 6 7 override fun onOptionsItemSelected(item: MenuItem): Boolean { 8 return when (item.itemId) { 9 R.id.settings -> { 10 startActivity(Intent(this@MainActivity,SettingsActivity::class.java)) 11 true 12 } 13 else -> super.onOptionsItemSelected(item) 14 } 15 }
These methods add the menu to the toolbar of our main application screen and add an action when settings is selected. We then register and unregister the listener in the appropriate callback methods like so:
1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 3 override fun onStart() { 4 super.onStart() 5 sharedPreferences.registerOnSharedPreferenceChangeListener(this) 6 } 7 8 override fun onDestroy() { 9 super.onDestroy() 10 sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) 11 }
Finally, let’s add the callback for the shared preference listener:
1// file: app/src/main/java/com/example/stockexchangeapp/MainActivity.kt 2 override fun onSharedPreferenceChanged(sharedPref: SharedPreferences?, key: String?) { 3 setupPusherChannels() 4 }
When the settings change, we call the setupPusherChannels
method again so it binds to the stock reports we enabled and unbind from those we disabled.
To complete our Pusher Beams setup, we need a service that will handle incoming notifications. Create a new class named NotificationsMessagingService
and paste this:
1// file: app/src/main/java/com/example/stockexchangeapp/NotificationsMessagingService.kt 2 // [...] 3 4 import android.app.NotificationChannel 5 import android.app.NotificationManager 6 import android.app.PendingIntent 7 import android.content.Intent 8 import android.os.Build 9 import android.preference.PreferenceManager 10 import android.support.v4.app.NotificationCompat 11 import android.support.v4.app.NotificationManagerCompat 12 import android.util.Log 13 import com.google.firebase.messaging.RemoteMessage 14 import com.pusher.pushnotifications.fcm.MessagingService 15 16 class NotificationsMessagingService : MessagingService() { 17 18 override fun onMessageReceived(remoteMessage: RemoteMessage) { 19 val sharedPref = PreferenceManager.getDefaultSharedPreferences(this) 20 MyStockList.stockList.forEachIndexed { index, element -> 21 val refKey = element.name.toLowerCase() + "_preference" 22 val refValue = sharedPref.getBoolean(refKey, false) 23 if (refValue && element.name == remoteMessage.notification!!.title!!){ 24 setupNotifications(remoteMessage) 25 } 26 } 27 } 28 29 private fun setupNotifications(remoteMessage: RemoteMessage) { 30 val notificationId = 10 31 val channelId = "stocks" 32 lateinit var channel:NotificationChannel 33 val intent = Intent(this, MainActivity::class.java) 34 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 35 val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) 36 val mBuilder = NotificationCompat.Builder(this, channelId) 37 .setSmallIcon(R.mipmap.ic_launcher) 38 .setContentTitle(remoteMessage.notification!!.title!!) 39 .setContentText(remoteMessage.notification!!.body!!) 40 .setContentIntent(pendingIntent) 41 .setPriority(NotificationCompat.PRIORITY_DEFAULT) 42 .setAutoCancel(true) 43 44 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 45 val notificationManager = applicationContext.getSystemService(NotificationManager::class.java) 46 val name = getString(R.string.channel_name) 47 val description = getString(R.string.channel_description) 48 val importance = NotificationManager.IMPORTANCE_DEFAULT 49 channel = NotificationChannel("stock-exchange", name, importance) 50 channel.description = description 51 notificationManager!!.createNotificationChannel(channel) 52 notificationManager.notify(notificationId, mBuilder.build()) 53 54 } else { 55 val notificationManager = NotificationManagerCompat.from(this) 56 notificationManager.notify(notificationId, mBuilder.build()) 57 } 58 } 59 60 }
In the code above, when a new message comes, we check if we enabled price reporting for that stock. With this information, we know whether to display the notification for it or not.
Next, open the strings.xml
file and add the following to the file:
1<!-- File: app/src/main/res/values/strings.xml --> 2 <string name="settings">Settings</string> 3 <string name="channel_name">Stock-Exchange</string> 4 <string name="channel_description">To receive updates about stocks</string>
Next, open the AndroidManifest.xml
file and update as seen below:
1<!-- File: ./app/main/AndroidManifest.xml --> 2 3 <application 4 [...] 5 > 6 7 // [...] 8 9 <service android:name=".NotificationsMessagingService"> 10 <intent-filter android:priority="1"> 11 <action android:name="com.google.firebase.MESSAGING_EVENT" /> 12 </intent-filter> 13 </service> 14 15 </application>
In the AndroidManifest.xml
file, add the internet permission as seen below:
1<manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="com.example.stockexchangeapp"> 3 4 <uses-permission android:name="android.permission.INTERNET"/> 5 6 [...] 7 8 </manifest>
With this, our Android application is complete. Let us now build our backend server.
Now that we have completed building the application, let us build the backend of the application. We will build our backend with Node.js.
Create a new folder for your project. Navigate into the folder and create a new package.json
file, then paste the following code:
1// File: ./package.json 2 { 3 "name": "stockexchangeapp", 4 "version": "1.0.0", 5 "description": "", 6 "main": "index.js", 7 "scripts": { 8 "test": "echo \"Error: no test specified\" && exit 1" 9 }, 10 "keywords": [], 11 "author": "", 12 "license": "ISC", 13 "dependencies": { 14 "@pusher/push-notifications-server": "^1.0.0", 15 "body-parser": "^1.18.3", 16 "express": "^4.16.3", 17 "pusher": "^2.1.2" 18 } 19 }
This file contains the meta data for the Node application. It also contains the list of dependencies the application relies on to function properly.
Next, let’s create a configuration file that will hold all our sensitive keys. Create a file named config.js
and paste this:
1// File: ./config.js 2 module.exports = { 3 appId: 'PUSHER_CHANNELS_APPID', 4 key: 'PUSHER_CHANNELS_KEY', 5 secret: 'PUSHER_CHANNELS_SECRET', 6 cluster: 'PUSHER_CHANNELS_CLUSTER', 7 secretKey: 'PUSHER_BEAMS_SECRET', 8 instanceId: 'PUSHER_BEAMS_INSTANCEID' 9 };
NOTE: Replace the placeholders above with keys from your Pusher dashboard.
Finally, let’s create another file named index.js
and paste this:
1// File: ./index.js 2 3 // import dependencies 4 const express = require('express'); 5 const bodyParser = require('body-parser'); 6 const path = require('path'); 7 const Pusher = require('pusher'); 8 const PushNotifications = require('@pusher/push-notifications-server'); 9 10 // initialise express 11 const app = express(); 12 const pusher = new Pusher(require('./config.js')); 13 const pushNotifications = new PushNotifications(require('./config.js')) 14 15 function handleStock(req, res, stock) { 16 let loopCount = 0; 17 18 let sendToPusher = setInterval(() => { 19 loopCount++; 20 const changePercent = randomIntFromInterval(-10, 10) 21 const currentValue = randomIntFromInterval(2000, 20000); 22 const stockName = (stock === 'amazon') ? 'Amazon' : 'Apple' 23 const price = currentValue.toString() 24 25 // Send to pusher 26 pusher.trigger('stock-channel', stockName, {currentValue, changePercent}) 27 28 pushNotifications.publish( 29 ['stocks'],{ 30 fcm: { 31 notification: { 32 title: stockName, 33 body: `The new value for ${stockName} is: ${price}` 34 } 35 } 36 }).then((publishResponse) => { 37 console.log('Just published:', publishResponse.publishId); 38 }); 39 40 if (loopCount === 5) { 41 clearInterval(sendToPusher) 42 } 43 }, 2000); 44 45 res.json({success: 200}) 46 } 47 48 app.get('/stock/amazon', (req, res) => handleStock(req, res, 'amazon')); 49 app.get('/stock/apple', (req, res) => handleStock(req, res, 'apple')); 50 51 function randomIntFromInterval(min,max) { 52 return Math.floor(Math.random()*(max-min+1)+min); 53 } 54 55 const port = 5000; 56 57 app.listen(port, () => console.log(`Server is running on port ${port}`));
This code above contains the endpoints for our application. We have two endpoints, one to handle all the processes for the amazon
stock, and the other for the apple
stock. We have the handleStock
method that basically does all the work.
In the folder directory, run this to install the modules:
$ npm install
Then run the following code to start the application:
$ node index.js
Now, if you run your app, you should see something like this:
In this post, we have learned how to leverage the power of Pusher to create powerful engaging applications.
You can find the source code on GitHub. Feel free to clone and explore further.