Android App - Free PacktPub Ebook Notifier (Weekend Project)

PacktPub is one of the great ebook and videos site for tech users and they offer one free ebook everyday. Most of the time, i forget to visit the site so i missed lot of free good ebooks and regret it later. So, i decided to create a weekend side project to explore kotlin language and also to refresh my android skills by understanding latest andriod O background processing limitations.

It took some time to understand the basic kotlin language concepts (like companion objects, null safety) but its a good oppertunity for .Net developers to explore this language rather than coding in java for android apps. Kotlin also provides some real good extensions plugins to eliminate lot of boilerplate android code like findviewbyid.

Application Overview

This app launches the main activity with asynctask in background to parse the content to get the free ebook title, image url and render it on the main activity. It also check the site periodically (based on settings) and notify the user.

class MainActivity : AppCompatActivity(),ToolbarManager {

private val Tag: String = "MainActivity"
private lateinit var parserHelper: ParserHelper
private lateinit var alarmManagerHelper: AlarmManagerHelper
var mImageURL: String? = null
var mTitle: String? = null
override val toolbar by lazy { find<toolbar>(R.id.toolbar) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initToolbar()
toolbarTitle = getString(app_name)

claimThisBook.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(getString(R.string.FREE_BOOK_URL)))
startActivity(intent)
}

//Call the Async Task
LoadContentTask().execute()

//Setting the Broadcast Alert
alarmManagerHelper = AlarmManagerHelper(this)
alarmManagerHelper.setBroadCastAlert(false)

//Enable the BootReceiver
enableBootReceiver()
}

private fun enableBootReceiver() {
val receiver = ComponentName(this, BootReceiver::class.java)
val pm = this.packageManager
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)
}

inner class LoadContentTask : AsyncTask<unit unit="">() {
override fun doInBackground(vararg p0: Unit) {
parserHelper = ParserHelper(this@MainActivity);
val (title, imageURL) = parserHelper.parsePacktPubFreeBook()
mTitle = title
mImageURL = imageURL
}

override fun onPostExecute(result: Unit) {
super.onPostExecute(result)
Glide.with(this@MainActivity).load(mImageURL).into(imageView)
titleView.text = mTitle
}
}
}

I have created AlarmManagerHelper class to set the repeating alarm based on the sync frequency settings. The Sync Frequency Settings are stored in SharedPreferences.

internal class AlarmManagerHelper(ctx: Context) : ContextWrapper(ctx) {

fun setBroadCastAlert(argRefreshAlarm: Boolean) {

var refreshAlarm = argRefreshAlarm
val alarmManager = this.getSystemService(Activity.ALARM_SERVICE) as AlarmManager

val notificationIntent = Intent(Constants.ALARM_RECEIVER_INTENT_TRIGGER)
notificationIntent.setClass(this, NotificationReceiver::class.java)

var pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_NO_CREATE)
if (pendingIntent == null) {
pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
refreshAlarm = true
}
if (refreshAlarm) {
val syncFrequency: String by DelegatesExt.preference(this, Constants.SYNC_FREQUENCY_NAME, Constants.SYNC_FREQUENCY_DEFAULT_VALUE)
val syncFrequencyLong = syncFrequency.toLong()
var finalCalInMillis: Long = System.currentTimeMillis() + (syncFrequencyLong * AlarmManager.INTERVAL_HOUR)
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, finalCalInMillis, (syncFrequencyLong * AlarmManager.INTERVAL_HOUR) , pendingIntent)
}
}
}

From Android O, there are lot of limitations on background execution limits and you can read all those details in android developer guide. For this project, i need a background job that should run periodically based on frequency settings defined in the app and send the notification to user with the book title. I used JobIntentService to do the background work and send the notification to user

class NotificationService : JobIntentService() {

private lateinit var notiHelper: NotificationHelper
private lateinit var parserHelper: ParserHelper

companion object {
private const val JOB_ID = 1000
private const val NOTI_PRIMARY = 1100

fun enqueueWork(ctx: Context, intent: Intent) {
enqueueWork(ctx, NotificationService::class.java, JOB_ID, intent)
}
}

override fun onHandleWork(intent: Intent) {

notiHelper = NotificationHelper(this)
parserHelper = ParserHelper(this);

val(title) = parserHelper.parsePacktPubFreeBook()

val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
notiHelper.notify(NOTI_PRIMARY, notiHelper.getNotification(getString(R.string.noti_title), title,pendingIntent))
}
}

I also created BootReceiver with BOOT_COMPLETED intent so that repeating alarm will get set even in the event of phone is restarted.

class BootReceiver : BroadcastReceiver() {

private lateinit var alarmManagerHelper: AlarmManagerHelper

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
alarmManagerHelper = AlarmManagerHelper(context)
alarmManagerHelper.setBroadCastAlert(false)
}
}
}

I used Jsoup Library to parse the HTML content. Jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods.

internal class ParserHelper(ctx: Context) : ContextWrapper(ctx) {

private val tag: String = "ParserHelper"

data class ParserEntity(val title: String, val imageURL: String?)

fun parsePacktPubFreeBook(): ParserEntity {
var title = "Internet access not available"
var imageURL: String? = null
if (isInternetConnected()) {
try {
val htmlContent = Jsoup.connect(getString(R.string.FREE_BOOK_URL)).get()
title = htmlContent.select("div[class=dotd-title]").text()
imageURL = "http:" + htmlContent.select("img[class=bookimage imagecache imagecache-dotd_main_image]").attr("src")
} catch (e: Exception) {
Log.e(tag, "Error in fetching the content " + e.printStackTrace())
}
}

return ParserEntity(title, imageURL)
}

private fun isInternetConnected(): Boolean {
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetworkInfo
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
}
}

So, overall this project will give you the idea of how to develop an android app in kotlin including running the background job and send the notitfication to users. I have uploaded the entire source code in github.

Happy Coding