During this pandemic, I have been keeping tabs on what is going via Al Jazeera. They had a site dedicated to providing live updates and I was inspired to create such an app. I followed them because of how they implemented some features that other sites didn’t. Here are those features that impressed me:

  1. Sectioning Live Corona Updates based on dates.
  2. Showing the Latest Updates at the top of the page.
  3. Endless scroll pagination, where as you reach the end of the page, more news are auto-loaded. You can go all the way up to when coronavirus started, at which they tell there is no more data.
  4. The Live Updates are mostly short and some contain images, some don’t. Some are simply one paragraph, some require you to open a new page to read more.
  5. All these updates are published by only a specific team.
  6. Human-Friendly dates like today,yesterday etc.

So I decided to create an app like that, an app like Al Jazeera Corona Updates site. The app will have the following features:

Demo

Here is the demo:

Features

(a). Kotlin Programming Language

We use Kotlin as our programming languge this time round. We’ve been doing a couple of Java projects of late but this app suites Kotlin as it(Kotlin) is the next generation programming language for android development.

(b). MVVM

As we do with almost all our projects, we want to to write high quality code using the latest design patterns. We will use Model View ViewModel which is the recommended pattern and is made possible by android architecture components.

(c). Firebase Realtime Database

The news articles will be stored in firebase realtime database. Featured Image URLs will be stored alongside the updates. We will see how to perform all CRUD operations against firebase.

(d). Firebase Cloud Storage

The featured images for our corona updates will be stored in firebase cloud storage. We will see how to perform CRUD operations involving images and text.

(e). Firebase Authentication

Yeah we will be authenticating our admins and editors using Firebase Authentication. This way we have control over who publishes/updates/deletes data. The app can be used by non-authenticated users but they can only read the corona updates. The app is designed to accomodate only a specific group of editors/admins. Therefore we’ve included the login component but no registration component. Admins/Editors are added at the Firebase console and not from the app itself. This makes it quite secure and avoids spam registrations.

You won’t be needing to login every time into the app. You login once only and the next time you come back we auto-sign you in even with no internet. If you sign out you would need to login again.

(f). Works even Offline

The app allows users/editors/admins to work even while completely offline. So an admin/editor can publish a news article while offline? Then how will the rest of the world read the news? Well the the moment the user regains a connection, the app will automatically publish it online as well.Meanwhile offline you can even make changes or even delete the news. If you delete it it won’t be published online. However if you select a featured image, we will automatically attempt to publish it online since we need image uploaded to the cloud storage and that is impossible if you are offline. So you can publish text while offline and when you regain connection, update it this time including an image. So in short, eveything works while completely offline apart from uploading of image.

(g). Endless Firebase Pagination

You will learn how to use firebase pagination. You can control the number of items you want fetched per page. In this case we use the endless pagination to stream in the live updates

(h). Search with Highlighting

You will also learn how to search and filter data based on a query. The search results are highlighted. We use a toolbar searchview, thus saving space.

(i). Sectioning By dates

Just like the Al Jazeera Live Corona Updates site, we will be sectioning our updates based on dates. For A section may be something like today,yesterday, tomorrow etc. We use a beautiful library known as karamba to provide intuitive date management.

(j). Latest Updates at the top

We show you how to organize updates using the latest-Top approach, that is the latest news coming at the top of the page. This feature may seem simple but with Firebase realtime database and when using Pagination, it is not obvious to implement because of known limitations of firebase realtime database.

Lesson 1: Creating and Setting up Project in Android Studio

Here are the steps we follow in creating and setting up our android studio project:

(a). Creating Android Studio Project

  1. Open up android studio.
  2. Go to File -> New -> New Project
  3. Choose Empty Activity then click Next
  4. Type your application name, select source language as Kotlin, minimum sdk as 19.
  5. Click Finish.

(b). Or Just Download the Project

Or download the project, extract it and simply and open it in android studio.

Lesson 2: Setting Up Firebase

We have a detailed step by step section on how to setup Firebase here. We also have a detailed section on how to add admins/editors via Firebase Console here and also enable email/password authentication. Please scroll over to the appropriate sections in those tutorials if you are not sure how to setup Firebase.

Lesson 3: Gradle Scripts, Dependencies and their roles

In this lesson we will setup our gradle scripts, the depedencies we need and their roles.

(a). Setting Up Kotlin Project For Data Binding

We are writing our android project in kotlin. We want to take advantage of data binding. Data binding reduces the amount of boilerplate we write when setting values to and from our UI widgets. We can set properties to, for example our textviews and edittexts implicitly instead of everytime invoking the setText() method or text property.

To setup data binding in kotlin follow the following steps:

  1. At the top of your app level build.gradle file add the following statement:
    apply plugin: 'kotlin-kapt'
  2. Inside the android closure add the following:
    dataBinding {
        enabled = true
    }

    Sync your project and that’s it. We’ve setup our project for data binding.

(b). Registering jitpack

By default android studio uses two repositories: jcenter and google. These are the places where android studio searches for libraries, in case you specify a dependency to be added into the project. However some libraries are hosted in jitpack, so we need to make sure android studio knows that:

  1. Go to project-level(located in root folder) build.gradle and add maven jitpack as below:
allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

(c). Dependencies we use

Here are the dependencies we will use in this project:

1. AndroidX Packages

These are the core packages we need for android development:

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'

2. Lifecycle Components

These will architect our app based on Model View ViewModel pattern. Lifecycle components will give us two main classes:

  1. ViewModel
  2. LiveData

Our ViewModel classes will extend the ViewModel class. ViewModel is a lifecycle conscious class and that will give us the ability to cache variables that persist across device orientation changes. We’ll use a type of ViewModel called AndroidViewModel.

LiveData on the other hand will give us live data as you can guess. When expose an object via LiveData, it can be observed. The observers receive changes that occur on the data in realtime. We’ll use a type of livedata called MutableLiveData.

Moreover we’ll have the ViewModelProvider which will help with instantiating our ViewModel class.

Here are the dependencies we need to use the above classes:

    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'

(b). Firebase Dependencies

With firebase we are interested in achieving three objectives:

  1. Be able to save text and perform all crud opertions on them.
  2. Be able to save images and perform all crud operations on them.
  3. Be able to authenticate users.

The first will be achieved by firebase realtime database. The second will be by Firebase cloud storage while the last by Firebase Authentication.

Here are their dependencies:

    implementation 'com.google.firebase:firebase-database:19.3.0'
    implementation 'com.google.firebase:firebase-storage:19.1.1'
    implementation 'com.google.firebase:firebase-auth:19.3.1'

(c). Image Loading, caching and Rendering

Images, we said, will be stored in firebase cloud storage. Their URLs will be stored in firebase realtime database. We therefore need a tool to downlaod those images based in the URLs, render them and even cache them locally. Their are several options for that:

  1. Picasso
  2. Glide
  3. Fresco etc.

We will us the first. All the above libraries are great generally easy to use.

Here is how we install Picasso:

    implementation 'com.squareup.picasso:picasso:2.71828'

Then below is how we will use Picasso to download images from the network:

    @JvmStatic
    fun loadImageFromNetwork(imageURL: String, fallBackImage: Int, imageView: ImageView) {
        if (imageURL.isNotEmpty()) {
            Picasso.get().load(imageURL).placeholder(R.drawable.load_dots).into(imageView)
        } else {
            Picasso.get().load(fallBackImage).into(imageView)
        }
    }

(d). Rendering Circular,Rectangular,Shaped Image Views

Sometimes you normally need to render shaped images. You need to add more capality to the ordinary imageview class to do that. The following are options for doing that:

  1. Circular ImageView library
  2. Shaped ImageView library.

I will use the second library. This library only gives us the ability to render circular imagesviews but also rectangular imageviews with curved edges.

Here is how we install it:

    implementation 'cn.gavinliu:ShapedImageView:0.8.6'

(e). Using any Custom Font

You may need to apply any type of font downloaded from online into your app. The best library for that is calligraphy. I use it almost everytime I want to design an app with custom fonts.

It has a dependency called ViewPump and we have to install them both:

    implementation 'io.github.inflationx:calligraphy3:3.1.1'
    implementation 'io.github.inflationx:viewpump:2.0.3'

(f). Beautiful Material Dialogs

Dialogs are important when you want to include in-app messaging in an interactive manner. Toasts normally show only for a limited time and auto-hide themselves. Dialogs on the other hand demand to be clicked to be dismissed. Morever we can use dialogs to show options to be selected. We will use LoveLyDialogs library:

    implementation 'com.yarolegovich:lovely-dialog:1.1.0'

(g). Material datepicker

Dates are extremely important in this app.This is a news app and in such apps a date is everything. People don’t want to be subscribing to outdated news. Heck, this is a coronavirus app and we want to be showing the latest news and statistics to our audiences.

We will use Material Datepicker library for picking dates. Explicitly assigning dates to posts is important since we want the editors to have full control of the date of publishing. For example an editor may want to show a previously published news as the latest news. Well he/she simply has to change the date of publishing. In doing so the latest news will appear at the top.

Here is how to install material datepicker:

    implementation 'com.shagi:material-datepicker:1.3'

(h). Image Slider

If you check a lot of news sites online, you will see that they do have image sliders,galleries,carousels and lots and lots of images. Well this is because images catch the attention of the user. Not only that but we humans are visual creatures. An image can pass a message quicker than reading texts.

We want to include the ability to heavily use images in our app. We will be showing images in our recyclerview. However we will also be showing images in a beautiful auto slider. The images will automatically slide themseslves after a specified interval, in a loop.

We use CarouselView and here’s how we install it:

    implementation 'com.synnapps:carouselview:0.1.4'

(i). EasyAdapter

An adapter is super important in almost every type of project you create. Be it a recyclerview adapter, view pager adapter etc. They are fundamental to how android apps interact with lists of data. Therefore having an easy way, free of as much boilerplate code as possible is important.

The library we’ve chosen to use in this project is easy adapter. It uses data binding, which we are also using in our project, thus allowing us to write a full adapter in very few lines of code.

Here is how we install it:

    implementation 'com.dc.easyadapter:easyadapter:2.0.3'

(j). Dexter

We want to handle runtime permissions gracefully. Note that runtime permissions are important only in devices of API level 23 and above. Basically we check for permissions at runtime and give the user the option to go assign them if they aren’t granted. Dexter is one of the best and easiest.

Here is how we install dexter:

    implementation 'com.karumi:dexter:4.2.0'

(k). ImagePicker

This is a simple library that will allow us:

  1. Pick Images from gallery.
  2. Pick images from file explorer.
  3. Capture images via camera.

It requires API 19 and above. Here’s how we install it:

    implementation 'com.github.maayyaannkk:ImagePicker:1.0.4'

(l). Karumba

Dates are vital to our news application, we’ve said that before. We therefore need an easy and powerful library to help us with Dates management. We will also need a library capable of showing human-readable date formats like: today,yesterday,monday etc. Karumba is such a library.

    implementation 'com.github.matteocrippa:karamba:1.2.0'

Lesson 4: Preparing Custom Fonts

We said we want the capability to include custom fonts into the app. And we’ve already downloaded the required librarys. Now:

  1. Go online and search fonts you want
  2. Download them in ttf format.
  3. Create a folder inside the main directory and call it assets.
  4. Add the downloaded fonts there.

That’s it. Now later in we will simply load them into our project.

Lesson 5: Preparing Transition Animations

We want to be applying smooth animations while transiting from one activity to another. First we need to write the animations. Note that some of the animations will be applied to widgets in our splash activity.

  1. Go to res directory and create a folder names anim.
  2. Add the animations. Actually if you download the project you will find the animations there.
  3. Now go to styles.xml in the downloaded project. You will we have loaded the animations and applied them to our activities e.g:
    <style name="MyActivityAnimations" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
        <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
    </style>

Then to apply them:

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowAnimationStyle">@style/MyActivityAnimations</item>
    </style>

Lesson 6: Preparing Drawables and Shapes

Any image you want to use in the project, come and add it here. Also later on while preparing your app for production, be sure to reduce the size of some of those images and remove the unnecesary ones as a way of reducing the final APK size.

Lesson 7: Preparing Menu resources

Some of our activities will have toolbar menus. Specifically our Listings Activity, Detail Activity and Publish Activity. These toolbar menus provide us with a contextual way of working within our activities.

Here will be our toolbar menus:

  1. detail_page_menu – Toolbar Menu for our detail page.
  2. edit_item_menu – menu for our Publish Activity, however only when in editing/deleting mode.
  3. listings_page_menu – menu for our listings page.
  4. new_item_menu – menu for our Publish activity, however only when in new-item mode.

Lesson 8: Preparing Value resources

These include:

  1. colors.xml – You will find colors defined here.
  2. dimens.xml – You can put all your dimensions here.
  3. strings.xml – If you want to change the app name, come here.
  4. styles.xml – You will find styles here.

Lesson 9: Layouts

You will find layout definitions here. Here are the layouts in the project and their roles:

No. Layout Role
1. _state.xml This layout will show our progress, success as well as error messages. It will be re-used, placed at the top of any activity that needs it.
2. activity_account.xml This layout will define how our account activity looks like.
3. activity_dashboard.xml Will be inflated into our dashboard activity UI.
4. activity_detail.xml Will be inflated into our detail page.
5. activity_listings.xml To be inflated into our listings page
6. activity_login.xml To be iflated into our Login activity.
7. activity_piblish.xml To be inflated into our Publish activity.
8. activity_splash.xml To be inflated into our splash activity
9. layout_progress To be included at the bottom of our recyclerview. Shown as we load more data
10. model.xml To be inflated into a single view item of our recyclerview.

Lesson 10: Creating Our Model Classes

We will have two model classes:

  1. News – To represent a covid-19 news update.
  2. RequestCall – To represent a single firebase request/operation we make/perform.

(a). News

This class will represent a single news or covid-19 update.

Here is the News class:

package info.camposha.aljazeeracorona.data.model.entity

import com.google.firebase.database.Exclude
import java.io.Serializable

/**
 * Our News Class. It's roles are:
 * 1. Define the properties of our News item.
 * 2. Assign Default News values using the elvis operator
 */
class News : Serializable {
    var title: String? = ""
    var content: String? = ""
    var date: String? = ""
    var sortKey: String?=""
    var publisher: String? = ""
    var imageURL: String? = ""

    @get:Exclude
    @set:Exclude
    var key: String? = ""

    @get:Exclude
    @set:Exclude
    var sectionHeader: String = ""

    override fun toString(): String {
        return title!!
    }

    override fun equals(n1: Any?): Boolean {
        if(n1 == null ){
            return false
        }
        val n: News = n1 as News

        if(n.key.isNullOrEmpty()){
            return false
        }
        return key==n.key
    }

    override fun hashCode(): Int {
        var result = title?.hashCode() ?: 0
        result = 31 * result + (sortKey?.hashCode() ?: 0)
        return result
    }

}

The class is implementing Serializable interface. This will enable us easily pass it’s instance across activities.The class then overrides two methods equal() and hashcode(). These two make it safe to compare the equality of the instance of this class with other instances.

(b). RequestCall

This class will represent a single firebase operation we make.

/**
 * This class will represent a single request or Firebase operation
 */
class RequestCall {
    //A single request will have the following attributes
    var status = 0
    var message: String = "NO MESSAGE"
    var news: List<News> = ArrayList()
}

This class’s instances will hold the following data:

  1. status – status of the operation e.g progress,success or error.
  2. message – message associated with the status e.g progress message, success message or error message.
  3. news – a list of news articles.

Lesson 11: Constants.kt

This class will hold our application constants.

A Constants is a variable whose value doesn’t change throughout our application lifetime. We need these types of variables in our projeccts.

package info.camposha.aljazeeracorona.common

import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import info.camposha.aljazeeracorona.R

object Constants {
    //The following line points us to the location of our database
    @JvmField
    val DB = FirebaseDatabase.getInstance()
        .getReference("aljazeera_corona_db")
    //The following points us to where our images will be stored
    @JvmField
    val IMAGES_DB = FirebaseStorage.getInstance()
        .getReference("aljazeera_corona_images_db")

    //Allowed editors
    const val ADMIN_EMAIL = "clarkkent@gmail.com" //password 123456
    const val EDITOR_1_EMAIL = "johndoe@gmail.com" //password 123456
    const val EDITOR_2_EMAIL = "jamesweb@gmail.com" //password 123456

    //Different states of our requests
    const val FAILED = -1
    const val IN_PROGRESS = 0
    const val SUCCEEDED = 1

    //An Array of local images. We will show one of them randomly as our dashboard banner
    @JvmField
    val LOCAL_IMAGES = intArrayOf(
        R.drawable.corona_icon, R.drawable.coronavirus_dont_panic
    )
}

Lesson 12: CacheManager.kt

This object will cache simple variables for us.

package info.camposha.aljazeeracorona.common

import info.camposha.aljazeeracorona.data.model.entity.News

object CacheManager {
    @JvmField
    var CURRENT_USER = ""

    @JvmField
    var MEM_CACHE = ArrayList<News>()

    @JvmField
    var SEARCH_STRING = ""

}

Sometimes you need a variable accessible from different parts of an application. For example , take the CURRENT_USER above. We need this string in almost all our activities. We will therefore cache it statically in a JvmField. We do the same thing for our MEM_CACHE will cache our news items instead of every time querying Firebase. However, we will be emptying it when we navigate away from the activity that needs it.

Lesson 13: PermissionManager.kt

This type of app needs permission management. For example, obviously you want non-logged in users to not be capable of modifying or adding news items. In this app only admins and editors can login. The other memebers of public can only view/read news. We won’t give them the privileges to modify the app content.

Because only admins and editors can log into the app, we will give them the full permissions to add/update/delete news. It’s as simple as the code below:

    val isLoggedIn: Boolean
        get() = CURRENT_USER.isNotEmpty()

Basically the current user variable, which we had defined in our CacheManager will cache our current user email. If it is empty then the user hasn’t logged in.

Now let’s see how tou can create custom permissions and assign them to various users. First you will have to define a list of pre-defined users. Obviously editors/admins can’t be that many so we can simply hold them in a simple array or as constants:

    //Allowed editors
    const val ADMIN_EMAIL = "clarkkent@gmail.com" //password 123456
    const val EDITOR_1_EMAIL = "johndoe@gmail.com" //password 123456
    const val EDITOR_2_EMAIL = "jamesweb@gmail.com" //password 123456

We had defined them in our Constants class.

Now let’s say you want to give only admins the ability to publish news, then here is how you do it:

    fun canPublishNews(): Boolean {
        return if (!isLoggedIn) false else CURRENT_USER === Constants.ADMIN_EMAIL
    }

Obviously if the user is not logged in then we deny him by returning fasle. We only return true if the user is admin. Now to actually use the method, for example say we are in the dashboard and somebody clicks the card that should give way to the publishing activity:

        addCard!!.setOnClickListener {
            if (PermissionManager.canPublishNews()){
                openActivity(this, PublishActivity::class.java)
            }else{
                promptLogin(this,"NOT ALLOWED","You need to Login First")
            }
        }

You can see from the above that we check if the user is an admin, if not then we show him a dialog prompting him to login. If he/she is an admin then we open the activity.

These permissions will be included in our PermissionsManager class:

object PermissionManager {
    val isLoggedIn: Boolean
        get() = CURRENT_USER.isNotEmpty()

    fun canPublishNews(): Boolean {
        return if (!isLoggedIn) false else CURRENT_USER === Constants.ADMIN_EMAIL
    }
    fun canEditNews(): Boolean {
        return if (!isLoggedIn) false else CURRENT_USER === Constants.ADMIN_EMAIL
                || CURRENT_USER === Constants.EDITOR_1_EMAIL
                || CURRENT_USER === Constants.EDITOR_2_EMAIL
    }
    fun canDeleteNews(): Boolean {
        return if (!isLoggedIn) false else CURRENT_USER === Constants.ADMIN_EMAIL
    }
}

You can add as many permissions as you like based on the above simple methods.

Lesson 14: Utils.kt

This class will contain general utility methods. These methods are re-usable methods and are mostly needed in more than one class. We make these methods static. As static method is a method that belongs to a class as opposed to an instance of a class. In kotlin, we typically use what is known as an object class rather than the ordinary class. The object class is defined using the keyword object as opposed to classe.g:

object Utils {

}

Here are some of the functionalities we include in our object class:

(a). Opening another activity

The following method will open another activity:

    @JvmStatic
    fun openActivity(c: Context, clazz: Class<*>?) {
        val intent = Intent(c, clazz)
        c.startActivity(intent)
    }

(b). How to send data from one activity to another(and receive them)

The following method will send data from one activity to another. The data sent is actually a full object. We are able to send an object across activities since these objects are serialized:

    /**
     * Send a news to another activity
     * @param c - Context
     * @param news - The News to be sent
     * @param clazz - Target activity
     */
    @JvmStatic
    fun sendToActivity(c: Context, news: News?, clazz: Class<*>?) {
        val i = Intent(c, clazz)
        i.putExtra("_KEY", news)
        c.startActivity(i)
    }

Here is how we receive the sent object:

    /**
     * Receive the sent News
     * @param intent
     * @param c
     * @return
     */
    @JvmStatic
    fun receive(intent: Intent, c: Context?): News? {
        try {
            return intent.getSerializableExtra("_KEY") as News
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

(c). How to show an info dialog

Info Dialog can be used to show messages within the app. We use the wonderful LovelyDialogs library to create our dialogs:

    /**
     * How to create an info dialog
     * @param activity - hosting the dialog
     * @param title - title of the dialog
     * @param message - message in the dialog
     */
    @JvmStatic
    fun showInfoDialog(activity: AppCompatActivity, title: String?, message: String?) {
        LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
            .setTopColorRes(R.color.indigo)
            .setButtonsColorRes(R.color.darkDeepOrange)
            .setIcon(R.drawable.flip_page)
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("Relax") { }
            .setNegativeButton("Go Back") { activity.finish() }
            .show()
    }

You pass the dialog title and message. You can also change the top color, buttons color, icon, button texts etc. We also react to dialog button clicks.

(d). How to prompt login action

Because we are implementing access control in this app, we will need to only show the user a login-required message but also an option to move to login page. A Dialog is perfect for that:

    /**
     * Prompt Login Dialog if user doesn't have permission to view current page
     */
    @JvmStatic
    fun promptLogin(activity: AppCompatActivity, title: String?, message: String?) {
        LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
            .setTopColorRes(R.color.colorPrimary)
            .setButtonsColorRes(R.color.colorAccent)
            .setIcon(R.drawable.flip_page)
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("Got You!") { }
            .setNeutralButton("Login") {
                openActivity(activity, LoginActivity::class.java)
            }
            .show()
    }

(e). How to Extract Image URLs from list of News items

Once our news have been downloaded, we will need their image urls so that we can show render the images in our beautiful carousel view on top of our page. The following method will allow us extract the image urls and return them as an string array:

    /**
     * Extract image URL from downloaded data
     * @param news - the lists
     * @return
     */
    @JvmStatic
    fun getImageURLs(news: List<News>): Array<String?> {
        val imageURLs = arrayOfNulls<String>(news.size)
        news.withIndex().forEach { (i, _News) ->
            imageURLs[i] = _News.imageURL
        }
        return imageURLs
    }

(f). How to Filter News based on a Query

We have an arraylist of news items and we want to filter based on a query:

    /**
     * Filter a list of News items based on a query
     */
    @JvmStatic
    fun filter(query: String, news: List<News>): ArrayList<News> {
        val hits = ArrayList<News>()
        for (n in news) {
            if (n.title!!.toLowerCase(Locale.getDefault()).contains(query.toLowerCase(Locale.getDefault()))) {
                hits.add(n)
            }
        }
        return hits
    }

(g). How to get Date today

We want to obtain the date today. The following method will get us the date and return it as a string:

    /**
     * Get date today
     */
    @JvmStatic
    fun getDateToday(): String {
        val sdf = SimpleDateFormat("yyyy-MM-dd")
        val currentDate = sdf.format(Date())
        return currentDate
    }

(h). How to convert a string to a date

The following method will receive a string and attempt to convert it to a Date oject. If this process fails, we return null.

    /**
     * Let's convert string to date using expression body
     */
    @JvmStatic
    fun convertDate(date: String): Date? = try{
        date.toDate("yyyy-MM-dd")
    }catch (e: Exception){
        null
    }

(i). How Filter News based on a Date

You have a list of corona updates and you want to get updates for a single day only:

    /**
     * Filter a list of news items based on a date
     */
    @JvmStatic
    fun getNewsForThisDate(allNews: List<News>?, date: String): ArrayList<News> {
        val filteredNews = ArrayList<News>()

        if (allNews == null) {
            return filteredNews
        }
        for (n in allNews) {
            if (n.date.equals(date,true)) {
                filteredNews.add(n)
            }
        }
        return filteredNews
    }

 

(j). How to Group our News items by dates

Because we will be sectioning our coronavirus updates based on dates of publishing, we need a way to group news items based on dates. Then return a nested list containing sections alongside news items. The below method will do that for us:

    /**
     * Return a List of Lists. Return news grouped by their publishing dates
     */
    @JvmStatic
    fun getAllNewsGroupedByDates(allNews: ArrayList<News>): ArrayList<ArrayList<News>> {
        val newsGroupedByDates = ArrayList<ArrayList<News>>()
        val remainingNews = ArrayList<News>()
        remainingNews.addAll(allNews)

        for (n in allNews) {
            if (n.date != null) {
                val currentGroupNews = getNewsForThisDate(remainingNews, n.date.toString())
                if (currentGroupNews.size > 0) {
                    if (!newsGroupedByDates.contains(currentGroupNews)) {
                        newsGroupedByDates.add(currentGroupNews)
                        for (currentInnerNews in currentGroupNews) {
                            remainingNews.remove(currentInnerNews)
                        }
                    }
                }
            }
        }
        return newsGroupedByDates
    }

(k). How to check if an update already exists in our cache

When doing pagination, you need a way to know if an item has already been downloaded. This prevents wasting of bandwith as we know whether we have already reached the last page of our data in the cloud. The following method will check for us if an update has already been downloaded and exists in our cache:

    /**
     * This method will:
     * 1. Check if a news item already exists in our cache.
     * This is useful for pagination
     * @param key
     * @return
     */
    @JvmStatic
    fun itemAlreadyExists(news: News): Boolean {
        var found = false
        for (n in MEM_CACHE) {
            if (n.sortKey === news.sortKey && n.key === news.key) {
                found = true
            }
        }
        return found
    }

(l). How to Create a Unique Descending Key
This method is very very important for us to be able to create this al jazeera like corona updates. One of the main funcionalities we want to implement in our app that is similar to the al jazeera coronavirus updates site is the ability to show latest news first. This may seem obvious but it’s quite tricky to do it in a firebase while also implementing pagination.

The reason is that firebase does not provide a way to sort data at the cloud in a descending manner. The auto-generated firebase keys are always ascending and it doesn’t make sense to reverse them in the client side since we are implementing pagination. A clever solution is to generate keys on the client. Those keys need to be descending and also unique. Then save the keys alongside the data. Then at the cloud, sort the data based on those custom keys.

The following method will create for us descending keys:

    /**
     * This method is extremely important for us to be able to sort our
     * data by dates, showing latest added news first
     */
    @JvmStatic
    fun createKey(str: String?): String {
        if(str == null || convertDate(str)==null){
            return System.currentTimeMillis().toString()
        }
        val d= convertDate(str)
        val m = d!!.month()*15*3
        val day = d.day()
        val y = d.year()*4
        val addition=(y+day+m)

        return (10000 - addition).toString()+"_"+(Long.MAX_VALUE-System.currentTimeMillis()/addition)
    }

Lesson 15: NewsViewModel.kt

Next we conme to our ViewModel class. It’s purpose is to expose functionality and data to the UI. As we are using MVVM principles, we want to decouple business logic from UI. Doing UI normally involves lots of boilerplate code. These boilerplate code would make testing our core app functionality hard. If we decouple them the testing becomes easier.

Moreover decoupling reduces chances of errors. It also makes maintaining the core code easier. We can also easily re-use that core code. ViewModel will act as that interface that connects our core business logic to our UI code.

Here is our ViewModel code:

package info.camposha.aljazeeracorona.viewmodel

import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import info.camposha.aljazeeracorona.data.model.entity.News
import info.camposha.aljazeeracorona.data.model.process.RequestCall
import info.camposha.aljazeeracorona.data.repository.NewsRepository

class NewsViewModel(application: Application) :AndroidViewModel(application) {
    private val newsRepository: NewsRepository = NewsRepository()

    fun publishOnConnect(news: News): MutableLiveData<RequestCall> {
        return newsRepository.saveLocally(news)
    }

    fun publishImmediately(news: News, imageUri: Uri): MutableLiveData<RequestCall> {
        return newsRepository.publishImageText(news, imageUri)
    }

    fun updateImageText(news: News, imageUri: Uri): MutableLiveData<RequestCall> {
        return newsRepository.updateImageText(news, imageUri)
    }

    fun updateOnlyText(news: News): MutableLiveData<RequestCall> {
        return newsRepository.saveLocally(news)
    }

    fun delete(news: News): MutableLiveData<RequestCall> {
        return newsRepository.deleteImageText(news)
    }

    val allNews: MutableLiveData<RequestCall>
        get() = newsRepository.select()

    fun search(searchTerm: String): MutableLiveData<RequestCall> {
        return newsRepository.search(searchTerm)
    }

    fun login(email: String, password: String): MutableLiveData<RequestCall> {
        return newsRepository.login(email, password)
    }

}

Lesson 16: BaseActivity.kt

Even though we are using fancy design patterns like MVVM. Our core paradigm is still Object orientation. Hence we need to follow object oriented pillars, of which inheritance is one of the most important.

Therefor we can apply this to our activities, make a base activity that encapsulates common functionalities that can then be inherited by other activities.

Here are some of those functionalities:

(a). Instantiate of ViewModel

We will need our ViewModel instance in more than one activity. We can define a method to instantiate it once and re-use across all those activities:

    protected fun newsViewModel(): NewsViewModel{
        //return ViewModelProviders.of(this).get(NewsViewModel::class.java)
        return ViewModelProvider(this).get(NewsViewModel::class.java)
    }

The commented line is the deprecated way of instantiating a ViewModel.

(b). Opening a Page

Yeah we did define how to open a page in our Utils class. However a better way is to define it here in the base activity so that we won’t need to be passing context objects:

    protected fun openPage(clazz: Class<*>?) {
        val intent = Intent(this, clazz)
        startActivity(intent)
    }

(c). Showing a Toast message

As simple as it sounds, showing a toast message involves quite some boilerplate. However wouldn’t it be nice if we would only be passing the message to be shown in a simple show() method:

    protected fun show(message: String?) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }

(d). Opening setting page

Because we want to implement a runtime permissions in our app. This will ensure that we can request users’ using devices with API 23+ the permissions at runtime. To make it easier for them, we will give them a dialog to take them directly to the settings page so that they can assign us permissions:

    private fun openSetting() {
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
        val uri = Uri.fromParts("package", packageName, null)
        intent.data = uri
        startActivityForResult(intent, 101)
    }

And to show that dialog:

    protected fun showSettingDialog() {
        Log.i("PERMISSION", "Showing Permission Setting Dialog")
        val builder =
            AlertDialog.Builder(this)
        builder.setTitle("Assign Permissions")
        builder.setMessage("This app needs permission to use this feature. You can grant them in app settings.")
        builder.setPositiveButton("GO TO SETTING") { dialog: DialogInterface, _: Int ->
            dialog.cancel()
            openSetting()
        }
        builder.setNegativeButton("Cancel") { dialog: DialogInterface, which: Int -> dialog.cancel() }
        builder.show()
    }

(e). Showing Progress Card

Another perfect functionality to include here is the progress card or message card. Actually it’s not just a progress card since we’ll use it to also show error as well as success messages.

Here is the code to create it:

    private fun createStateCard(title: String?, msg: String?, isShowing: Boolean, isLoading: Boolean, STATE: Int) { //state widgets
        val handler = Handler()
        val delayedHiding =
            Runnable { progressCard!!.visibility = View.GONE }
        if (isShowing) {
            progressCard!!.visibility = View.VISIBLE
            if (isLoading) {
                pb!!.visibility = View.VISIBLE
            } else {
                pb!!.visibility = View.GONE
            }
            titleTV!!.text = title
            msgTV!!.text = msg
            when (STATE) {
                FAILED -> {
                    titleTV.setTextColor(resources.getColor(android.R.color.holo_red_dark))
                    msgTV.setTextColor(resources.getColor(android.R.color.holo_red_light))
                    handler.postDelayed(delayedHiding, 10000)
                }
                IN_PROGRESS -> {
                    titleTV.setTextColor(resources.getColor(android.R.color.holo_blue_dark))
                    msgTV.setTextColor(resources.getColor(android.R.color.holo_blue_light))
                }
                SUCCEEDED -> {
                    titleTV.setTextColor(resources.getColor(android.R.color.holo_green_dark))
                    msgTV.setTextColor(resources.getColor(android.R.color.holo_green_light))
                    handler.postDelayed(delayedHiding, 10000)
                }
            }
        } else {
            progressCard!!.visibility = View.GONE
        }
        closeBtn!!.setOnClickListener { v: View? ->
            progressCard.visibility = View.GONE
        }
    }

That card will have several states like progress,error and success. Each state will have its color.

Lesson 17: BaseEditingActivity.kt

Our editing activities will inherit from this more specialized activity.
Here are the functionalities it will define:

(a). EditTexts Validation

The following method will validate our edittexts:

    protected fun validate(vararg editTexts: EditText): Boolean {
        if ( editTexts[0].text == null ||  editTexts[0].text.toString().isEmpty()) {
            editTexts[0].error = "This field is Required Please!"
            return false
        }
        if (editTexts[1].text == null || editTexts[1].text.toString().isEmpty()) {
            editTexts[1].error = "This field is Required Please!"
            return false
        }
        if (editTexts[2].text == null || editTexts[2].text.toString().isEmpty()) {
            editTexts[2].error = "This field is Required Please!"
            return false
        }
        return true
    }

(b). Clearing EditTexts

The following method will clear our edittexts:

    protected fun clearEditTexts(vararg editTexts: EditText) {
        for (editText in editTexts) {
            editText.setText("")
        }
    }

(c). Getting EditTexts Value

An easier and safer way of obtaining values from edittexts is:

    protected fun valOf(editText : EditText): String {
        return editText.text.toString()
    }

The above method will make sure that we don’t pass a null edittext.

(d). Showing Material Datepicker dialog

The below method will show a material datepicker dialog and allow us set the selected date onto an edittext:

  protected fun selectDate(dateTxt: EditText) {
        dateTxt.setOnClickListener {
            val dialog =
                DatePickerFragmentDialog.newInstance { view: DatePickerFragmentDialog?, year: Int, monthOfYear: Int, dayOfMonth: Int ->
                    val month: String
                    val monthOfYear1 = monthOfYear+1
                    month = if (monthOfYear1 < 10) {
                        "0$monthOfYear1"
                    } else {
                        monthOfYear1.toString()
                    }
                    val day: String = if (dayOfMonth < 10) {
                        "0$dayOfMonth"
                    } else {
                        dayOfMonth.toString()
                    }
                    dateTxt.setText("$year-$month-$day")
                }
            dialog.show(supportFragmentManager, "DATE_PICKER")
        }
    }

Lesson 18: AccountActivity.kt

A Logged In user will be able to view his account details. Account details aren’t much as we are directly working with Firebase Authentication, which stores only our email and password. Password is not sent to the app for security purposes. The account activity will have tabs with different sections.

Here are the features and functionalities with this activity:

(a). Tabs

Tabs allows us to divide a page into several sections. We are using TabLayout. Our sections will be represented by TabItem objects:

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@color/colorPrimaryDark"
        app:tabGravity="fill"
        app:tabIndicatorColor="@color/white"
        app:tabSelectedTextColor="@color/white"
        app:tabTextColor="#FFFFFF">

        <com.google.android.material.tabs.TabItem
            android:id="@+id/contactTab"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="DETAILS"/>

        <com.google.android.material.tabs.TabItem
            android:id="@+id/missionTab"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="Mission" />

    </com.google.android.material.tabs.TabLayout>

Here is how we will lsiten to tablayout selection events:

        tablayout.addOnTabSelectedListener(object : OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab) {
                if (tab.position == 0) {
                    detailsLayout.visibility = View.VISIBLE
                    missionLayout.visibility = View.GONE
                } else {
                    detailsLayout.visibility = View.GONE
                    missionLayout.visibility = View.VISIBLE
                }
            }

            override fun onTabUnselected(tab: TabLayout.Tab) {}
            override fun onTabReselected(tab: TabLayout.Tab) {}
        })

When this activity is resumed, we will set the CURRENT_USER email to our email textview:

    override fun onResume() {
        super.onResume()

        emailTV.text = CacheManager.CURRENT_USER
    }

Lesson 19: DashboardActivity.kt

The Dashboard will act as our homepage. From this page we will be able to navigate to other pages. The dashboard is beautiful and contains cardviews which when clicked opens for us the desired page. These are completely customizable as most work for this activity are done in the XML layouts.

Here is how will react to click events of this cardviews. Check how we handle permissions:

    private fun initializeWidgets() { //We have 4 cards in the dashboard
        viewCard!!.setOnClickListener {
            openActivity(this, ListingActivity::class.java)
        }
        addCard!!.setOnClickListener {
            if (PermissionManager.isLoggedIn){
                openActivity(this, PublishActivity::class.java)
            }else{
                promptLogin(this,"NOT ALLOWED","You need to Login First")
            }
        }
        account!!.setOnClickListener {
            if (PermissionManager.isLoggedIn){
                openActivity(this, AccountActivity::class.java)
            }else{
                promptLogin(this,"INSUFFICIENT PRIVILEGES","You need to Login First")
            }
        }
        closeCard!!.setOnClickListener { finish() }
        logOutBtn.setOnClickListener{
            CacheManager.CURRENT_USER=""
            FirebaseAuth.getInstance().signOut()
            finish()
        }
    }

If the user clicks the logout button, first we set the current user to empty. The we signout the user by invoking the signOut method before finishing the current activity.

We will also be setting text property to our logOutBtn based on the status of the current user:

    override fun onResume() {
        super.onResume()
        if (PermissionManager.isLoggedIn){
            logOutBtn.text="LOG OUT"
        }else{
            logOutBtn.text="LOG IN"
        }
    }

Lesson 20: DetailActivity.kt

This is the activity that will render the news details. It provides for a better reading experience as the news are scrollable. We show the associated image,if present, on top on our CollapsingToolbarLayout. We will be receiving the clicked news via Intent and rendering the details:

    /**
     * We will now receive and show our data to their appropriate views.
     */
    private fun receiveAndShowData() {
        receivedNews = receive(intent, this@DetailActivity)
        if (receivedNews != null) {
            b!!.news = receivedNews
            loadImageFromNetwork(receivedNews!!.imageURL!!,
                LOCAL_IMAGES[Random().nextInt(LOCAL_IMAGES.size)], dImageView
            )
        }

        editFAB.setOnClickListener {
            if (PermissionManager.isLoggedIn){
                sendToActivity(this, receivedNews, PublishActivity::class.java)
                finish()
            }else{
                Utils.promptLogin(this, "INSUFFICIENT PRIVILEGES", "You need to Login First")
            }

        }
    }

Because of the power of data binding, we are not explicitly setting news details on their associated textviews. Instead data binding will do that for us. Because of that, our detail activity will have probably only half the amount of code it would have othersiwe had if we hadn’t used data binding.

Lesson 21: ListingActivity.kt

This class will be vital for us especially as we aim to create this type of app we are creating. This is where section our data will occur as well as pagination as well as search. All these are core objectives of this project as they are implemented in the al jazeera corona live updates webpage.

Here are some of the properties we will include as instance fields of this class:

    private var reachedEnd: Boolean = true
    private val mPostsPerPage: Int = 10
    private lateinit var adapter: EasyAdapter<News, ModelBinding>
    //We define our instance fields
    private var networkImages = arrayOf<String?>(
        "https://images.unsplash.com/photo-1580458072512-96ced1f43991?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80"
    )

Here is how we will be setting up our carousel view:

    private fun setupCarousel() {
        carouselView!!.setImageListener(imageListener)
        carouselView.setImageClickListener { position: Int ->
            show("Clicked item: $position")
        }
        carouselView.pageCount = networkImages.size
    }

Let’s look at how to work create our adapter which will provide our recyclerview with sections. First we instantiate the adapter:

        adapter = object : EasyAdapter<News, ModelBinding>(R.layout.model) {

We will then override the onBind() method:

            override fun onBind(binding: ModelBinding, n: News) {

Then we check if our section header is not empty:

                if(n.sectionHeader.isNotEmpty()){
                    binding.mSectionTV.text=n.sectionHeader
                    binding.mSectionTV.visibility=View.VISIBLE
                    binding.contentSection.visibility=View.GONE

If it is not empty we hide all the other text and image widgets apart from the sectionTV, the view that will render our section header.

Otherwise if our key is not null or empty:

                }else if(!n.key.isNullOrEmpty()){
                    binding.mSectionTV.visibility=View.GONE
                    binding.contentSection.visibility=View.VISIBLE
                    binding.mDateTV.text = n.date
                    binding.mTitleTV.text = n.title
                    binding.mContentTV.text = n.content

we start by hiding the section header while showing the content section and setting the appropriate values to the widgets in that section.
If our image url is not null or empty, we load the image via Picasso:

                    if (!n.imageURL.isNullOrEmpty()) {
                        Picasso.get().load(n.imageURL)
                            .placeholder(R.drawable.load_dots).error(R.drawable.image_not_found)
                            .into(binding.mImageView)
                    }else{
                        binding.mImageView.visibility= View.GONE
                    }

otherwise we dismiss the imageview.

When the user clicks the content section, we will take the clicked news item to the details page:

                binding.contentSection.setOnClickListener {
                    Utils.sendToActivity(a, n, DetailActivity::class.java)
                }

We will be highlighting our search results as the user types the search query in our searchview. Here is how we do so:

                /**
                 * The intention is to search already downloaded news articles
                 * while highlighting the search results
                 */
                val query = CacheManager.SEARCH_STRING
                if (query.isNotEmpty() && n.title!!.toLowerCase(Locale.getDefault())
                        .contains(query.toLowerCase(Locale.getDefault()))
                ) {
                    val startPos: Int = n.title!!.toLowerCase(Locale.getDefault())
                        .indexOf(query.toLowerCase(Locale.getDefault()))
                    val endPos: Int = startPos + query.length
                    val spanString =
                        Spannable.Factory.getInstance().newSpannable(binding.mTitleTV.text)
                    spanString.setSpan(
                        ForegroundColorSpan(Color.GREEN), startPos, endPos,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                    )
                    binding.mTitleTV.text = spanString
                }

We will finish setting up our adapter by setting the adapter and recyclerview properties:

        /**
         * Set adapter properties
         */
        adapter.setLoadMoreRes(R.layout.layout_progress)
        rv.layoutManager=LinearLayoutManager(this)
        rv.adapter=adapter
        setupCarousel()

Here is how we will setup our load more listener:

    /**
     * We want to implement an endless pagination. The following method will help
     * in identifying whether we've reached the bottom of the recyclerview
     */
    private fun setupLoadMoreListener(){
        fetchNews(null)
        adapter.setOnLoadMoreListener(rv, EasyAdapter.OnLoadMoreListener {
            if (!reachedEnd) {
                fetchNews(MEM_CACHE[MEM_CACHE.size-1].sortKey)
                return@OnLoadMoreListener true // Returns True if you have more data
            } else {
                return@OnLoadMoreListener false // Return false if you don't have more data
            }
        })
    }

If we haven’t reached the end, we will fetch more updates from firebase and return true, otherwise we simply return false.

When our activity is resumed, we will reset our search string, clear our memory cache and setup our load more listener.

    override fun onResume() {
        super.onResume()
        CacheManager.SEARCH_STRING=""
        MEM_CACHE.clear()

        setupLoadMoreListener()
    }

22. LoginActivity.kt

Our Login Activity will be a beautiful screen for logging in user:

    /**
     * Now login
     */
    private fun login() {
        if (!validate()){
            return
        }
        newsViewModel().login(emailTxt.text.toString(),passwordTxt.text.toString()).observe(this, Observer {
            if (makeRequest(it,"LOGIN")== Constants.SUCCEEDED){
                CacheManager.CURRENT_USER= FirebaseAuth.getInstance().currentUser?.email!!;
                if (PermissionManager.isLoggedIn){
                    openPage(DashboardActivity::class.java)
                    finish()
                }else{
                    show("Something is wrong. Email is null or empty")
                }
            }
        })
    }

We pass the email and password and observe the returned livedata for changes. If we fail, the error messages will be automatically shown in the progress/error/success card at the top of the page.

If we succeed, we will cache the current user in a static variable. We will then re-check the permissions to make sure the email is not null, after which we will open the dashboard page and finish the current activity.

We have some intereseting things going in our onResume():

    override fun onResume() {
        super.onResume()

        if(FirebaseAuth.getInstance().currentUser != null){
            CacheManager.CURRENT_USER= FirebaseAuth.getInstance().currentUser!!.email!!
            openPage(DashboardActivity::class.java)
            finish()
        }
        emailTxt.setText(Constants.ADMIN_EMAIL)
        passwordTxt.setText("123456")

    }

Every time this login activity resumes, we will check that the current user from firebaseauth instance is not null. If it isn’t we cache that user in our static variable, then open the dashboard page. That is a case of auto-signing in a previously signed in user. This will be the case even if the user reboots the device. The user won’t be required to everytime enter the login details.

If the current user is null, we will set defaul email and password(This is purely for demo purposes) so that a test user can easily test the app.

Lesson 23: PublishActivity

This is our editor activity. This activity will be used for:

  1. Publishing News
  2. Updating News
  3. Deleting News

We will work with both images and text. A user can easily publish news without any image or that containing both images and text.

The images can be obtained:

  1. By capturing them directly from camera.
  2. By choosing them from gallery.
  3. By choosing from filesystem via File explorer.

How to capture Image from camera/select via gallery or file explorer

We start by creating our capture method:

    /**
     * Capture or select image
     */
    private fun captureImage() {
        val i = Intent(this, ImageSelectActivity::class.java)
        i.putExtra(ImageSelectActivity.FLAG_COMPRESS, false) //default is true
        i.putExtra(ImageSelectActivity.FLAG_CAMERA, true) //default is true
        i.putExtra(ImageSelectActivity.FLAG_GALLERY, true) //default is true
        startActivityForResult(i, 1213)
    }

In that method we’ve specified the desired options.

We then simply receive the image via the onActivityResult and render it using picasso:

    /**
     * After capturing or selecting image, we will get the image path
     * and use it to instantiate a file object
     */
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK && data != null) {
            if (requestCode == 1213) {
                val filePath =
                    data.getStringExtra(ImageSelectActivity.RESULT_FILE_PATH)
                chosenFile = File(filePath)
                Picasso.get().load(chosenFile!!).error(R.drawable.image_not_found).into(topImageImg)
            }
        }
        resumedAfterImagePicker = true
    }

How to Check Runtime Permissions va Dexter

Dexter as we said earlier is a runtime permissions library. We can use it to check for permissions at runtime with the below code:

    /**
     * We use a library known as Dexter to check for permissions at
     * runtime.If we haven't been granted then we present the user with
     * a dialog to take him to settings page to grant us permission
     */
    private fun checkPermissionsThenPickImage() {
        Dexter.withActivity(this)
            .withPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
            .withListener(object : PermissionListener {
                override fun onPermissionGranted(response: PermissionGrantedResponse) {
                    show("Good...READ EXTERNAL PERMISSION GRANTED")
                    captureImage()
                }

                override fun onPermissionDenied(response: PermissionDeniedResponse) {
                    show("WHOOPS! PERMISSION DENIED: Please grant permission first")
                    if (response.isPermanentlyDenied) {
                        showSettingDialog()
                    }
                }

                override fun onPermissionRationaleShouldBeShown(
                    permission: PermissionRequest,
                    token: PermissionToken
                ) {
                    Log.i("DEXTER PERMISSION", "Permission Rationale Should be shown")
                    token.continuePermissionRequest()
                }
            }).check()
    }

If we haven’t been granted permissions we show the user a dialog asking him/her to go to settings page and grant us the permission.

How to Publish News with Featured Images

The following method will publish for us only text news:

    private fun publishOnlyText(news: News) {
        newsViewModel().publishOnConnect(news)
            .observe(this, Observer { r ->
                if (makeRequest(r, "NEWS PUBLISHING") == SUCCEEDED) {
                    show("News Text Published Successfully")
                    b!!.news = News()
                }
            })
    }

How to Publish News with Featured Image

The following method will publish news with a featured image:

    private fun publishImageText(news: News) {
        newsViewModel().publishImmediately(news, Uri.fromFile(chosenFile))
            .observe(this, Observer { r ->
                if (makeRequest(r, "NEWS PUBLISHING") == SUCCEEDED) {
                    show("News Publishing Successfully")
                    b!!.news = News()
                }
            })
    }

How to Update News without Updating featured Image

The following method will automatically be invoked if the user clicks the update button but he/she hasn’t selected an image:

    private fun updateOnlyText(news: News) {
        newsViewModel().updateOnlyText(news)
                .observe(this, Observer { r ->
                    if (makeRequest(r, "NEWS TEXT UPDATE") == SUCCEEDED) {
                        show("News Updated Successfully")
                        openPage(ListingActivity::class.java)
                        finish()
                    }
                })
    }

How to Update News including uploading a new Featured Image

The following method will automatically be invoked if the user clicks the update button and he/she has selected a new featured image:

    private fun update(news: News) {
        newsViewModel().updateImageText(news, Uri.fromFile(chosenFile))
                .observe(this, Observer { r ->
                    if (makeRequest(r, "NEWS UPDATE") == SUCCEEDED) {
                        show("News Updated Successfully")
                        openActivity(c, ListingActivity::class.java)
                        finish()
                    }
                })
    }

How to Delete a News Permanently

The following method will be used to delete the news item:

    private fun delete(news: News) {
        newsViewModel().delete(news)
                .observe(this, Observer { r ->
                    if (makeRequest(r, "NEWS DELETE") == SUCCEEDED) {
                        show("News Deleted Successfully")
                        openPage(ListingActivity::class.java)
                        finish()
                    }
                })
    }

Lesson 24: SplashActivity.kt

The following activity will be our splash activity. We will animate our splash activity widgets using animation classes:

class SplashActivity : AppCompatActivity() {
    var isAnimationFinished = false

    private fun setupAnimation() {
        val bounceAnim = AnimationUtils.loadAnimation(this, R.anim.bounce)
        appTitleTV.animation = bounceAnim
        bounceAnim.setAnimationListener(object : AnimationListener {
            override fun onAnimationStart(animation: Animation) {}
            override fun onAnimationEnd(animation: Animation) {
                isAnimationFinished = true
                Handler().postDelayed({
                    Utils.openActivity(this@SplashActivity,LoginActivity::class.java)
                }, 1000)
            }

            override fun onAnimationRepeat(animation: Animation) {}
        })
    }

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        setupAnimation()
    }

    override fun onResume() {
        super.onResume()
        if (isAnimationFinished) {
            finish()
        }
    }
}

That’s it.