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(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() {
        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

Android App - Free PacktPub Ebook Notifier (Weekend Project) Android App - Free PacktPub Ebook Notifier (Weekend Project) Reviewed by Jeeva Subburaj on April 19, 2018 Rating: 5

1 comment:

  1. It's nearly impossible to find knowledgeable people for this topic, however, you
    sound like you know what you're talking about! Thanks

    ReplyDelete

Powered by Blogger.