Today we want to look at a template you can use in creating any type of project that requires Kotlin PHP MySQL CRUD multipart as well as authentication, with advanced features like Disk caching, Pagination and Data Binding. You can use it in any project but for us we use the concept of a Famous Paintings app. An app that admins can post paintings, update them and delete them for other people to view. It’s a full android app covering major concepts. You can use it to learn full app android development as well.

What You will Learn

As a template and learning project, this app has been designed to mainly teach you the following concepts:

  1. Full App Android Development using Kotlin, PHP and MySQL.
  2. How to use Fast Networking Library to interact with RESTFul services.
  3. How to perform CRUD operations involving both images and text agains PHP MySQL server. Learn how to efficiently upload images alongside text, while showing upload progress. Also how to update, delete and download.
  4. How to write a full android app using Model View ViewModel and see it’s benefits with regards to seperation of concerns.
  5. How to create a highly fast and bandwith friendly android app making use of permanent Disk caching and that avoids unnecessary calls to the server. We rarely make calls to the server. We paginate our data. When we make a call, we send only single requests that do only a single thing in the server, ensuring fast response times from the server.
  6. How to make an app that is scalable and that can be hosted even in the cheapest of webhosts yet easily server hundreds of thousands of users. We do this by ensuring that the app is offline-first, downlaods data in chunks and doesn’t make a request to the server unless a user performs an action that requires we connect. This is mostly leaves our server untouched for other users to use. The data including the details of lists are cached permanently on disk, and gets auto-refreshed if we make a change or hit the refersh button.
  7. Learn to make an android mysql app that has authentication. Where admins can login into the app and do stuff and even view their account details. Other users on the other hand can only view data but not make an edit.
  8. Learn how to make an android app that has several pages like Splash screen, Dashboard page, Upload page, listings page, details page, login page as well as Accounts Page. Yet we abstract away the similarities in Base Activities, and take advantage of inheritance to pass over those properties to the child activities.
  9. Learn to create an app with several material widgets like material edittexts, material dialogs, material datepicker, collapsing toolbar layouts etc.
  10. Learn to create an animated app, with beautiful transition animations between pages. The pages slide beautifully in and out of view.
  11. Learn how to create an app that can interact with both secure https and non-secure http content (ClearText traffic).
  12. And many more.

By the Way

This is a premium project. We design projects like these to accelerate students learning. Put away books and articles for a moment and use an easy to understand yet well designed app to learn important concepts in a few days. We are always updating our projects and will be sending subscribers updates.

Major Technologies

Here are technologies you will learn from the project:

  1. Kotlin Programing Language.
  2. Object Oriented PHP 7.2
  3. MySQL
  4. RedbeanPHP ORM
  5. Model View ViewModel
  6. Data Binding
  7. Fast Networking Library
  8. Gson

Demo

Here is the demo of the project:

Step 1 – Creating Our Android Studio Kotlin Project

We start by creating our project. Follow the followings steps:

  1. Open up android studio.
  2. Go to File -> New -> New Project
  3. A Dialog will be shown. Select Empty Activity as our template and click Next.
  4. Type Your application Name, Choose Minimum SDK to API 19, Choose Language as Kotlin.
  5. Click Finish.

A New android studio project will be created with a MainActivity.kt and activity_main.xml. We will delete both later on and create our own files. Let’s now move to next step.

TakeAways

Well so in this step what did we learn? Well, how to create a Kotlin Android Studio project.

Lesson 2 – Configure Gradle Scripts.

We now need to make some few configurations to help us in be able to use certain tools in our project.
1.First go to root-level(located in root folder/project folder) build.gradle and register jitpack as a repository:

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

We do this since we will be downloading some libraries from jitpack as opposed to jcenter and google which are android studio’s default repositories.

2.Now come the app-level build.gradle file and add enable data binding by adding the following code under the android closure:

    dataBinding {
        enabled = true
    }

Data Binding will save us from lots of boilerplate findViewById code. It will also allow us to easily set data to several widgets.

If you would love to change the minimum sdk, compile sdk version etc, you change them in the app-level build.gradle file we’ve looked at.

TakeAways

So what are the takeaways from this lesson? Well:

  1. How to add jitpack as a repository.
  2. How to enable data binding.

Lesson 3 – Installing Our Dependencies

To create a full app like this, we will need a couple of dependencies. These make our life easier and make us more productive. We avoid re-invention of the wheel. These dependencies are always called libraries. Let’s see some of the dependencies we will be using and what they bring to the table.

1. AndroidX Libraries

We are using the latest APIs provided by android. Here are the androidx libraries we will be installing:

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'

These are basic libraries that give us the core APIs we will use in our project. In essence,

2. Arch-Components Libraries

We’ve rightly advertised that we are using Model View ViewModel(MVVMM) in this project. This design pattern will allow us to correctly organize our project into modules that are testable and re-usable. Android makes this process easier by providing us the LiveData and ViewModel classes. However we need to install the SDK package containing these. We do so with the following statement:

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

3. AndroidNetworking(Fast Networking Library)

Let’s also install this beautiful HTTP Client. It is actually an essential of this project since our aim is to learn how to use Fast networking library instead of retrofit for our HTTP needs. We install it using the following statement:

    implementation 'com.amitshekhar.android:android-networking:1.0.2'

4. Gson

Gson is another beautiful library when it comes to HTTP stuff. However it isn’t a HTTP Client. Rather it is a JSON library. We can use it to encode to or decode from JSON(Javascript Object Notation). This is vital since our data will be downloaded in JSON format.

One way can be us simply parsing our JSON data manually using JSONObject,JSONArray and AsyncTask classes. Or we can simply use Gson to map the JSON to our data objects. Let’s use the latter approach.

    implementation 'com.google.code.gson:gson:2.8.6'

Gson is an optional library for this project. You can replace it with another library or simply use the JSONObject and JSONArray classes.

5. Reservoir

We said we would be supporting full offline browsing of data. Our aim is always to make fast,convenient and economical apps. Being able to cache data to Disk is a must in this scenario. We don’t want to be making unnecessary HTTP calls to the server every now and then to be just downloading the same data set. It would be much better to just downlaod the data once and cache them to disk and only attempt to download more data if the user reaches the end of the list or hits the refresh button. This way we save user’s bandwith and make it possible to host our app backend even with cheap shared hosts. Yet still the app can be used by hundreds of thousands of users.

The library we use is an old horse called reservoir. Install it using the following command.

    implementation 'com.anupcowkur:reservoir:3.1.0'

It does employ LRU(Least Recently Used) mechanism of caching.

THis library is optional. Ommit it if you don’t want to support permanent caching in the app.

6. Picasso

Am always one of the Picasso diehards. As in Picasso, the artist. I have always been in love with his work. It’s amazing that today we are creating an app for famous paintings and Picasso’s works are certainly among the top. If you see abstract works of arts listed in the app, that are difficult to decipher, they may be Picasso’s.

So it makes sense to use the beautiful Picasso image loader library as well. We start by installing it:

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

Picasso is an optional library in this project. You can replace it easily with Glide or any of your favorite imageloaders. You can even implement one youself using AsyncTask.

7. Material DatePicker

When it comes to the world of publishing,Dates are certainly very important. A Date informs you of when an event occured. Or in this case when a post or painting was uploaded. You can then order paintings through dates at the server side. I will use a library I use oftenly in my projects for date picking, Material DatePicker.

This library will create for us a DatePicker fragment. We can then return the selected date. Install this library using the following command:

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

This library is optional. Ommit it if you don’t want to include dates, or if you intend to pick dates automatically using the java.util.Date class.

8. LovelyDialogs

In an app like this, we will need several ways of showing messages to the user. One way of that is using Toasts. However Toasts are inflexible and un-interactive. Dialogs on the other ensure that you force the user to react to the message. They block the screen but can easily be dismissed by clicking exit button or clicking outside the dialog.

Moreover we will use our dialogs to receive inputs. We do this through Single-Choice Dialogs. Users can choose an item from the dialog and we return the chosen item. We do this for example using the Categories Dialog. Through this dialog our users can select a category for a painting.

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

Dialogs are optional. You can ommit them if you don’t want them. You can replace the single-choice dialogs with spinners or auto-complete textviews. However they are better in my opinion since they are flexible and customizable. They also provide with a full window to show users some instructions.

9. ShapedImageView

ImageViews on steroids. That’s a way to put it. Basically rather than having the ordinary boring imageview, you get the following with this library:

  1. Circular ImageViews
  2. Round-Rectangular ImageViews.
  3. Bordered ImageViews.
  4. Normal imageview.

It’s basically an extension of the imageview class. Install it using the following statement:

    implementation 'cn.gavinliu:ShapedImageView:0.8.6'

This is a very optional library. You can ommit it completely or use another library.

10. CarouselView

One of our aims when making this apps is to make a modern app that employs good UI design. One of the things that probably mobile development has borrowed from web is to have something like a jumbotron or gallery or even video at the top of the page.

For us we will have a gallery. Actually a CaourselView. An image slider. It will slide our images, flipping them after a couple of seconds with nice animations. For this app the slider will appear great even in small devices.

The images being flipped are the ones downloaded alongside our data. We will extreact these images from our list of paintings and auto-flip them. Users can also swipe these images from left to right and vice versa.

    implementation 'com.synnapps:carouselview:0.1.4'

This library is optional in this project. Ommit it if you don’t want the image slider.

 

11. ImagePicker

Well not just an imagepicker. This library will allow us to:

  1. Capture images from camera.
  2. Select images from gallery.
  3. Select images from file system.

It’s ease of use is admirable. The only caveat is that it is what is forcing us to upgrade to a minimum SDK support level of API 19. The imagepicker works across all devices but the camera may be problematic in some devices, just like many other camera libraries.

You install it using the following statement:

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

This library is optional. Ommiting it would allow you degrade the minimum SDK supported to API 16. However you would have to implement code for capturing/picking images.

12. EasyAdapter

Are you tired of always having to write lots of boilerplate code for your recyclerview adapters. Especially if you intend to use multiple recyclerviews with different adapters. If then you are in luck. EasyAdapter uses data binding to allow you write your adapter code in as little as three lines, literally. Yet it’s flexible enough if you want to ovveride the onCreateViewHolder method or even extend it.

Let’s install and use it:

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

EasyAdapter is optional. You can ommit it if you want to write your own adapter or use another library.

13. Calligraphy

What is the easiest way to inject custom fonts in your application? A way that is also flexible and reliable? Well, use Calligraphy, the most popular Font library for android. It’s easy and works with a variety of fonts well. I use this library almost in all my projects since frankly it doesn’t have any worthy replacement.

Let’s install alongside ViewPump, it’s dependent:

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

TakeAways

So what have we learned in this lesson?

  1. Importance of several third party libraries.
  2. How to install them.

Lesson 4 – Preparing Custom Fonts

As we said ealier, we are employing Calligraphy to load any font of our desire. There are plenty of fonts online so experiment. Once you’ve downloaded the fonts, come create an assets folder inside the main directory, just next to java and res. Call it assets and add the fonts there. We will load these fonts into our app in our Application class.

While packaging your app for production, be sure to remove unused fonts to reduce APK size.

TakeAways

  1. How to add custom fonts into our project.

Lesson 5 – Creating Animations

We will have two types of animations we create and use in our project:

  1. Activity Transition Animations
  2. Widget Transition Animations.

(a). Activity Transition Animations

If you download the APK at the end of this course, you will see that we as we move from one activity to another, we smoothly slide our activities in and out. This is really modern and beautiful and doesn’t happen by default. However it’s super simple to create it and apply it. We don’t need any Kotlin/Java code, only pure XML.

1.Go ahead and create a folder called anim inside the res directory.

In this directory we will place several animation files. You will find them in the project but let me show you two:

For slide_down.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="50"
        android:fromYDelta="0%p"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toYDelta="100%p" />

</set>

And for slide_in_left.xml:

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-100%p" android:toXDelta="0"
        android:duration="@android:integer/config_mediumAnimTime"/>
</set>

Now to apply them we have to move to the styles.xml in the values folder and create a style:

    <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 apply it our themes, the themes that will be applied to our activities, e.g

    <!-- Base application theme. -->
    <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>

(b). Widget Transition Animations

The next batch of animations are those which will be applied to individual widgets, like in our splash activity. If you go to the splash activity of the app, you will notice that we apply animations on our logo as well as title and subtitle. Let’s write those animations:

First is the drop.xml where our logo will be slightly dropped from a higher to lower position:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromYDelta="-150%"
        android:toYDelta="0%"
        android:duration="500"
        android:repeatCount="0" />
</set>

The next is our fade_in animations where we will fade in our title and subtitle:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="1500"
        />
</set>

This part is optional. If you don’t want the animations in your app, you can ommit this process and delete the anim folder.

TakeAways

We’ve seen:

  1. How to create custom activity transition and widget animation files.
  2. How to apply the activity transition animations on our activities.

Lesson 6 – Preparing Drawable Resources

Drawable resources are images,shapes and vector files you may need in your project. All the drawables we need are included in the drawables folder.

Takeways

  1. When building your APK, ready for production, remove unnecessary drawables. Resize bigger images. This is the fastest way to reduce the APK size of your app. During development however feel free to test with different images to achieve great design.

Lesson 7 – Preparing Value resources

Here are our value resources:
(a). arrays.xm – An array of colors
(b). colors.xml – Contains all our colors
(c). dimens.xml – Contains dimensions
(d). strings.xml – Use it to hold static strings
(e). styles.xml – Contains all our styles.

Lesson 8 – Creating our Toolbar Menus

Some of our activities will have toolbar menus. These menus assist in navigation. Here are our toolbar menus:
(a). detail_page_menu – Menu for detail page
(b). edit_item_menu – Menu for Upload Page but with editing mode on.
(c). listings_page_menu – Menu for Listings Page.
(d). new_item_menu – Menu for Upload Page but with editing mode off.

Lesson 9 – Creating our XML Layouts

We will have the following XML layouts:
(a). _state.xml – To be inflated into our progress card. Comprises of Progressbar and textviews placed in a viewgroup.
(b). activity_about_us.xml – To be inflated into our AboutUsActivity.
(c). activity_dashboard.xml – To be inflated into Dashboard Activity.
(d). activity_details.xml – To be inflated into DetailActivity.
(e). activity_listings.xml – To be inflated into Listings Activity.
(f). activity_login.xml – To be inflated into Login Activity.
(g). activity_splash.xml – To be inflated into splash activity.
(h). activity_upload.xml – To be inflated into Upload activity.
(i). model_grid.xml – To be inflated into recyclerview view items.

Lesson 10 – Our App class

This is our application class. It’s roles include:

  1. It’s where we do our pre-activity initializations. Initializations that we want don’t prior to the creation of activities.

How to Initialize reservoir

Reservoir is our LRU Disk caching library. It’s responsible for our disk caching needs and we want it initialized before our activities are created. There is no harm initializing it at the activity level. However, it’s a better approach initializing before activities are created.

Initialization involves specifying the number of bytes to be reserved for cache. In our onCreate() method of our App class we simply add:

    override fun onCreate() {
        super.onCreate()
        //Initialize disk caching
        initializeCache(this)
        //...

That initializeCache() method had been defined in our CacheManager class as follows:

    @JvmStatic
    fun initializeCache(c: Context?) {
        DISK_CACHE_INITIALIZED = try {
            Reservoir.init(c, 1000048) //in bytes
            true
        } catch (e: IOException) {
            // BUG - Don't invoke IOException methods like e.getMessage()
            Log.e("CAMPOSHA", "INITIALIZATION OF RESERVOIR FAILED")
            false
        }
    }

(b). How to Initialize AndroidNetworking

The next thing is to initialize our HTTP Client, the Fast networking library. This should also ideally be initialized prior to our activities creation. Here is how we initialize it:

        AndroidNetworking.initialize(this)

(c). How to Initialize Calligraphy

Calligraphy will also be initialized right here in the App class. Why? Well remeber Calligraphy is responsible for loading our fonts. We want the fonts to be loaded before activities are created since it’s on these activities where our fonts will be applied.

Here is how we initialized Calligraphy:

        ViewPump.init(
            ViewPump.builder()
                .addInterceptor(
                    CalligraphyInterceptor(
                        CalligraphyConfig.Builder()
                            .setDefaultFontPath("fonts/RobotoBold.ttf")
                            .setFontAttrId(R.attr.fontPath)
                            .build()
                    )
                )
                .build()
        )

Lesson 11 – Creating Painting Class

This will be our main model class. It’s what our app is about. It’s our business object. Let’s programmatically represent the properties of this important class:

/**
 * Let's Create our Painting class to represent a single Painting.
 * It will implement java.io.Serializable interface, a marker interface that will allow
 * our
 * class to support serialization and deserialization.
 */
class Painting : Serializable {
    /**
     * Let' now come define instance fields for this class. We decorate them with
     * @SerializedName
     * attribute. Through this we are specifying the keys in our json data.
     */
    @SerializedName("id")
    var id: String? = ""
    @SerializedName("name")
    var name: String? = ""
    @SerializedName("description")
    var description: String? = ""
    @SerializedName("author")
    var author: String? = ""
    @SerializedName("medium")
    var medium: String? = ""
    @SerializedName("period")
    var period: String? = ""
    @SerializedName("date")
    var date: String? = ""
    @SerializedName("image_url")
    var imageURL: String? = ""

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

Now we’ve made the class Serializable. This will facilitate passing the instances of the class across activities. You can also see that we’ve annotated the instance fields with special @SerializedName() attribute. Now the strings you pass onto these attributes will have to match the JSON keys so that Gson can successfully map the class to our JSON data.

Lesson 12 – Creating Admin class

This is the class to represent an admin. Admins will be required to sign into the app to access admin privileges like the ability to upload a painting,update,delete etc. Non-admins will only be able to view. Admins will be fetched from a different table in the database. You can add admins via web interface or directly using PHPMyAdmin. Then they can login into the app using the credentials you supply them.

Here’s the class:

/**
 * Let's Create our Admin class to represent a single Admin.
 * It will implement java.io.Serializable interface, a marker interface that will allow
 * our
 * class to support serialization and deserialization.
 */
class Admin : Serializable {
    /**
     * Let' now come define instance fields for this class. We decorate them with
     * @SerializedName
     * attribute. Through this we are specifying the keys in our json data.
     */
    @SerializedName("id")
    var id: String? = ""
    @SerializedName("name")
    var name: String? = ""
    @SerializedName("email")
    var email: String? = ""
    @SerializedName("bio")
    var bio: String? = ""
    @SerializedName("country")
    var country: String? = ""
    @SerializedName("image_url")
    var imageURL: String? = ""

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

You can ommit some of these properties.

Lesson 13 – Mapping JSON to a Kotlin Class using Gson

We now need a class that will represent our JSON response. Our data will be sent over the wire in JSON format. We can easily map that data to a Kotlin class without having to manually parse the JSON data. Gson will be responsible for this.

All we need is supply appropriate attributes, the way we’ve done thus far to our Painting and Admin classes. Here is our ResponseModel Class:

/**
 * Our json response will be mapped to this class.
 */
class ResponseModel {
    /**
     * Our ResponseModel attributes
     */
    @SerializedName("paintings")
    var stars: ArrayList<Painting>? = ArrayList()
    @SerializedName("code")
    var code: String? = "-1"
    @SerializedName("message")
    var message: String? = "UNKNOWN MESSAGE"
    @SerializedName("admin")
    var admin: Admin? = null
}

Try not to get those strings we pass onto our attributes wrong. If you get them wrong the app will throw malformed url exceptions. Thus make sure that your json data has specified those keys.

Lesson 14 – Representing our Process

When using Object Oriented Programming, it’s important to try and go full throttle when necessary. For example we can easily represent a single HTTP request with a simple class. This way we can define important properties that we want to pay attention in each and every request.

Here is our RequestCall class:

class RequestCall {
    var status = 0
    var message: String? = "UNKNOWN MESSAGE"
    var paintings: ArrayList<Painting>? = ArrayList()
    var admin: Admin? = null
}

What properties are we capturing in our requests:

  1. Status – Status of the request e.g SUCCEEDED,IN_PROGRESS and FAILED.
  2. Message – Message, e.g error message, success message and failure message. These will be returned from the server.
  3. Paintings – The List of downloaded paintings.
  4. Admin – Admin making that request.

Lesson 15 – Performing our Multipart CRUD Operations

We will create a class called PaintingsRepository. Since we are implementing MVVM design pattern, this logic class will be protected from the UI. It’s role will be to define the logic for interacting with our server. Of course as we’ve been saying, we use the Fast Networking Library.

Here are the methods we will create:

  1. upload() – Upload both Painting image and painting text to the server. These will be stored in mysql database.
  2. updateOnlyText() – Allow us to be more efficient, making an ordinary request instead of a multipart suppose the user doesn’t change the current painting image.
  3. updateImageText() – Make a multipart request, updating both the image and text. The uploaded image will replace the previous one and it’s path saved in the database.
  4. delete() – Delete both image and text from server.
  5. fetch() – Download a list of paintings from our mysql database, via PHP of course.
  6. login() – Login the admin to the app.

All these methods are defined in the PaintingsRepository, as we said. However let me show you the first method, the upload so that you see our coding style.

The below method will send an image and text details of a painting to the server. We will receive the image file to be uploaded as well as the Painting object. The Painting object will contain the properties that need to sent to the server.

    fun upload(s: Painting, image: File): MutableLiveData<RequestCall>{
        val r=RequestCall()
        r.status= IN_PROGRESS
        r.message="Uploading..Please wait"
        val mLiveData = MutableLiveData<RequestCall>()
        mLiveData.value=r
        AndroidNetworking.upload(Constants.BASE_URL+"index.php")
            .addMultipartFile("image", image)
            .addMultipartParameter("action", Constants.UPLOAD)
            .addMultipartParameter("name", s.name)
            .addMultipartParameter("description", s.description)
            .addMultipartParameter("author", s.author)
            .addMultipartParameter("medium", s.medium)
            .addMultipartParameter("period", s.period)
            .addMultipartParameter("date", s.date)
            .setTag("upload")
            .setPriority(Priority.HIGH)
            .build()
            .setUploadProgressListener { bytesUploaded, totalBytes ->
                // do anything with progress
                r.message= (bytesUploaded/totalBytes*100).toString()+" % Uploaded"
                mLiveData.postValue(r)
            }
            .getAsJSONObject(object : JSONObjectRequestListener {
                override fun onResponse(response: JSONObject?) {
                    r.status= SUCCEEDED
                    // do anything with response
                    if(response == null){
                        r.message="It seems your server is returning null"
                    }else{
                        val gson=Gson()
                        val rm = gson.fromJson(response.toString(),ResponseModel::class.java)
                        r.message=rm.message
                    }
                    mLiveData.postValue(r)
                }

                override fun onError(error: ANError) { // handle error
                    r.status= FAILED
                    r.message=error.message
                    mLiveData.postValue(r)
                }
            })
        return mLiveData
    }

Very clean! We will be returning LiveData objects which will be observed by susbcribers in our activities. We will be updating our progress card with messages as well as upload progress. Take note that the method is completely decoupled from the UI. You can easily test and re-use it in your projects. We’ve also handled null responses which is possible if you didn’t write good PHP code.

Lesson 16 – How to Login into Our App

We said admins can login. Signing in grants the user the ability to post/edit/delete a painting. We can’t worry about spam because other users cannot register into the system. The admins are added at the server level and the app simply allows them to login and do stuff, not register. This is a great way if you want an app where only a limited number of people can access certain privileges.

We did include the login() logic in our Paintings Repository class.

    fun login(email: String,password: String): MutableLiveData<RequestCall>{
        //Logic here.
    }

Then in our ViewModel, we include a method to expose that functionality to the UI:

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

The pr is the PaintingsRepository instance.

Then in the LoginActivity we start by doing validation then observing our ViewModel login() method:

    private fun login() {
        if (!validate()){
            return
        }
        remoteViewModel.login(emailTxt.text.toString(),passwordTxt.text.toString()).observe(this, Observer {
            if (makeRequest(it,"LOGIN")== Constants.SUCCEEDED){
                if (it.admin != null){
                    CacheManager.ADMIN=it.admin
                    PrefUtils.save(this, it.admin!!)
                    openPage(DashboardActivity::class.java)
                    finish()
                }else{
                    showInfoDialog("Whoopsss!", it.message!!,R.drawable.m_info)
                }
            }
        })
    }

You can see that if our returned admin is not null, we cache it in a static variable then save it permanently in the shared preferences before opening our dashboard. If it is null then we show the error message in an info dialog.

The purpose of the SharedPreferences is to save our admin login details so that we can auto-sign him/her the next time he visits the app, even after device restart. So in our onResume() method we will include code for checking if we have a user in sharedpreferences. If we have him then we simply send him to the dashboard page. No need to re-login every time:

    override fun onResume() {
        super.onResume()

        if (PrefUtils.load(this) != null){
            CacheManager.ADMIN=PrefUtils.load(this)
            show("Auto-signed you in")
            openPage(DashboardActivity::class.java)
            finish()
        }
        //...

In the project you will find the complete code as well as the PHP code.

Lesson – Rendering Painting Details in our Detail Activity

Well we will also need to show details of a single painting in our details activity. In the the Listings activity we were simply listing paintings. However users will be able to read more details about a given painting right here in this page. Data Binding technology will alleviate us from having to write lots of boilerplate code. We won’t write any findViewByIds. We won’t even need to use text property or setText() method of our textviews.

The first step is to initialize our ActivityDetailBinding to null:

    private var b: ActivityDetailBinding? = null

In the onCreate method:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        b = DataBindingUtil.setContentView(this, R.layout.activity_detail)

Now the next things is for us to create a function called receiveData():

    private fun receiveAndShowData() {

Then receive the sent Painting:

        receivedPainting = receive(intent)

We had by the way defined a method in our Utils class to re-use in receiving Painting objects sent from other activities:

    /**
     * This method will allow us receive a serialized painting, deserialize it and return it,.
     */
    @JvmStatic
    fun receive(intent: Intent): Painting? {
        try {
            return intent.getSerializableExtra("_KEY") as Painting
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

Now inside the receiveAndShowData method we first check if the Painting is not null. If not we simply set it to our b object, b remember is an instance of our ActivityDetailsBinding:

        if (receivedPainting != null) {
            b!!.p = receivedPainting

            loadImageFromNetwork(Constants.IMAGES_BASE_URL + receivedPainting!!.imageURL, R.drawable.nav_header_backgound, b!!.dImageView)
        }
        b!!.editFAB.setOnClickListener { goToEditingPage() }
    }

 

Lesson – Dashboard Activity

This Dashboard activity is meant to lead us to other pages.It’s more or less like our home page. We can use it as our cental navigation page. It doesn’t do anything magical and more work is done on the layout design side rather than the kotlin side. We will use a series of cards to lead way to other pages.

We also easen our job here using Data Binding:

    private var b: ActivityDashboardBinding? = null

Then in the onCreate():

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        b = DataBindingUtil.setContentView(this, R.layout.activity_dashboard)
        handleEvents()
    }

Here is the definition of that handleEvents() method:

    private fun handleEvents() { //We have 4 cards in the dashboard
        b!!.viewCard.setOnClickListener {
            openPage(ListingActivity::class.java)
        }
        b!!.addCard.setOnClickListener {
            openPage(UploadActivity::class.java)
        }
        b!!.aboutUs.setOnClickListener {
            openPage(AboutUsActivity::class.java)
        }
        b!!.closeCard.setOnClickListener { finish() }
        b!!.mCollapsingToolbarLayout.setExpandedTitleColor(resources.getColor(android.R.color.white))
    }

There is something really simple but important that will take place right here in this dashboard activity: Loading our user from sharedpreferences into variable cache:

    override fun onResume() {
        super.onResume()

        CacheManager.ADMIN=PrefUtils.load(this)
    }

And that’s it.That’s our Dashboard activity.

Lesson – AccountActivity

The next activity is the AccountActivity. This activity will only be accessible to logged-in users only. Admins can therefore view their account details via this layout. The details are as stupulated in our Admin class.

We will be using Tabs to navigate to different sections within this activity. Here are us listening to our TabLayout selection events:

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

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

Inside our onResume() method, we will recheck that the admin is not null. If it is we simply finish the activity. If it’s not then we assign him to our data binding property. This way the admin details will be shown in our details section of our Tabs. We don’t even have to call the setText() methods or even the text properties, saving us from writing lots of boilerplate code.

    /**
     * Recheck that admin is not null. If it's not then assign it to our data binding
     * property
     */
    override fun onResume() {
        super.onResume()

        if(CacheManager.ADMIN == null){
            show("You do not have permission to view this page")
            finish()
        }else{
            b!!.p= CacheManager.ADMIN
        }
    }

Lesson – Listings Activity

Another highly important page is our Listings Activity. This will be responsible for:

  1. Download Paintings from our server.
  2. Listing the Paintings in a Grid Recyclerview.
  3. Extracting images from our downloaded paintings and showing them in a CarouselView.
  4. Listening to our recyclerview scroll events and downloading more data when the user reaches the end of our list. This is what we call pagination.
  5. Listening to click events for a single recyclerview item and sending it’s details to the details page.

We will have a couple of instance fields defined in this class:

    private var b: ActivityListingsBinding? = null
    private var adapter: EasyAdapter<Painting, ModelGridBinding>? = null
    private var glm: GridLayoutManager? = null
    private val ITEMS_PER_PAGE = "10"
    private var isScrolling = false
    private var current = 0
    private var total = 0
    private var scrolledOut = 0
    private var networkImages = arrayOf<String?>()
    private var ALREADY_REACHED_END = false

Again we are using data binding. We will be using EasyAdapter to create our adapter. We will arrange items in a grid-like fashion using the GridLayoutManager class. If you want to control the number of items being downloaded from the server, change the ITEMS_PER_PAGE variable. isScrolling will hold for us the scroll state of our recyclerview. current will hold for us the current position of our pages, total the total pages and scrolledOut the number of pages that have been scrolled out. networkImages is an array that will hold for us the extracted images from our paintings. ALREADY_REACHED_END will inform us if we have already reached end previously so that we don’t make unnecessary call to the server. This improves efficiency which is of essence to us.

We will be listening to carosuelview image-switch events, as a result loading the next image using Picasso:

    private val imageListener =
        ImageListener { position: Int, imageView: ImageView? ->
            Picasso.get().load(
                networkImages[position]
            ).placeholder(R.drawable.placeholder).error(R.drawable.image_not_found).fit()
                .centerCrop().into(imageView)
        }

We need to setup our image slider, our carouselview:

    /**
     * Let's setup our image slider
     */
    private fun setupCarousel() {
        b!!.carouselView.pageCount = networkImages.size
        networkImages = getImageURLs(STARS_CACHE)
        b!!.carouselView.setImageListener(imageListener)
        b!!.carouselView.setImageClickListener { position: Int ->
            if (networkImages.isNotEmpty()) {
                val starName = getItemBasedOnImage(networkImages[position])
                show(starName)
            }
        }
        //hide carousel if there is no image
        b!!.carouselView.visibility = if (networkImages.isNotEmpty()) View.VISIBLE else View.GONE
    }

We’ve set the page count. It will correspond to the number of images we have in our array. If a user clicks an image in our carousel, we will search for the clicked Painting using the image url as the search id, showing it in a toast message. If there is no image we will hide the carouselview.

Let’s now setup our adapter class:

    /**
     * Setup our adapter using EasyAdapter
     */
    private fun setupStuff() {
        adapter = object : EasyAdapter<Painting, ModelGridBinding>(R.layout.model_grid) {
            override fun onBind(binding: ModelGridBinding, s: Painting) {
                binding.nameTV.text = s.name
                binding.numTV.text = getPosition(s)

                Picasso.get()
                    .load(Constants.IMAGES_BASE_URL + s.imageURL)
                    .placeholder(R.drawable.load_glass).error(R.drawable.image_not_found)
                    .into(binding.mImageView)
                binding.mCardView.setOnClickListener {
                    sendToActivity(a, s, DetailActivity::class.java)
                }
            }
        }
        glm = GridLayoutManager(a, 2)
        b!!.rv.layoutManager = glm
        b!!.rv.adapter = adapter
        listenToRecyclerViewScroll()
        setupCarousel()
    }

You can see how easy it is to use easyadapter. The code above includes the full adapter setup. In the onBind we are setting the name and position of our Painting. We are also laoding our image using Picasso. We are listening to click events, in the process sending the current painting to the details page via intent. We are also instanting our GridLayoutManager and setting it as the layout manager of our recyclerview. We are then setting the adapter to our recyclerview, then listening to our recyclerview scroll events then finally setting up our carousel.

What about actually fetching our Data? Well here is the method that does that:

    /**
     * This method will download for us data from php mysql based on supplied query string
     * as well as pagination parameters. We are basiclally searching or selecting data
     * without searching. However all the arriving data is paginated at the server level.
     */
    private fun fetch(start: String, limit: String) {
        remoteViewModel.fetch(start, limit)
            .observe(this, Observer { r: RequestCall ->
                val result = makeRequest(r, "DOWNLOAD")
                if (result == Constants.SUCCEEDED) {
                    CacheManager.CACHE_IS_DIRTY = false
                    if (r.paintings!!.size > 0) {
                        for (s in r.paintings!!) {
                            if (!itemAlreadyExists(s)) {
                                STARS_CACHE.add(s)
                                /*
                                we then dump the memory cache to disk for
                                permanent caching
                                */
                                cacheToDisk(STARS_CACHE)
                            }
                        }
                        //add current page to adapter
                        adapter!!.addAll(r.paintings, true)
                        adapter!!.notifyDataSetChanged()
                        //extract image urls from our list and setup carousel
                        networkImages = getImageURLs(STARS_CACHE)
                        setupCarousel()
                    } else {
                        show("Reached End")
                        ALREADY_REACHED_END = true
                    }
                }
            })
    }

The method is downloading our paintings from the server and adding them to the end our adapter which is then refreshed. After a successfull download we will reset our carouselview to account for the new images as well. However we will know we’ve reached the end of our list from the server if the server returns us an empty list. In that case we show the user a message and mark our ALREADY_REACHED_END property as true. For it to be set to false now, the user has to leave the listings activity and come back, or hit the reload button. This occurs because hitting the reload button starts the download from the start of the list.

We are implementing the Load more pagination or the endless pagination technique against our php mysql server. This will make our app efficient and cost-friendly in terms of bandwith usage. We only download what is needed. And even better, once we’ve made a download we completely cache it in the device so that the user doesn’t have to hit the server to access the data. We in the process reduce the number of requests made to our server. This will make our app hostable to even the cheapest webhosts like shared hosts and still be able to serve a multitude of users.

We however need to listen to the scroll events for our recyclerview to be able to achieve this:

    /**
     * We will listen to scroll events. This is important as we are implementing scroll to
     * load more data pagination technique
     */
    private fun listenToRecyclerViewScroll() {
        b!!.rv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(rv: RecyclerView, newState: Int) { //when scrolling starts
                super.onScrollStateChanged(rv, newState)
                //check for scroll state
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    isScrolling = true
                }
            }

            override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) { // When the scrolling has stopped
                super.onScrolled(rv, dx, dy)
                /*
                getChildCount() returns the current number of child views attached to the
                parent RecyclerView.
                */
                current = glm!!.childCount
                /*
                getItemCount() returns the number of items in the adapter bound to the
                parent RecyclerView.
                */
                total = glm!!.itemCount
                /*
                findFirstVisibleItemPosition() returns the adapter position of the first
                visible view.
                */
                scrolledOut = glm!!.findFirstVisibleItemPosition()
                if (isScrolling && current + scrolledOut == total) {
                    isScrolling = false
                    if (dy > 0) {
                        // Scrolling up
                        if(!ALREADY_REACHED_END){
                            fetch(total.toString(), ITEMS_PER_PAGE)
                        }else{
                            show("No More Data Found")
                        }
                    } else {
                        // Scrolling down
                    }
                }
            }
        })
    }

While listening to the scroll events, you can see we are tracking the current page, scrollout pages as well as total pages. We can get all these from our recyclerview properties. We only download data if the user has reached the end of the list and the user was scrolling up. If the user had reached the end initially and we didn’t find data, we just inform him of that without re-connecting to the server.

We will do some really important things in our onResume() method. Let’s have a look at it first:

    override fun onResume() {
        super.onResume()
        ALREADY_REACHED_END = false
        adapter!!.clear(true)
        //If cache is dirty then fetch fresh data
        if (CacheManager.CACHE_IS_DIRTY) {
            fetch("0", ITEMS_PER_PAGE)
        } else {
            //Load data from our hard disk cache
            loadFromDiskCache().observe(
                this,
                Observer { paintings: ArrayList<Painting> ->
                    if (paintings.size > 0) { //add our stars to memory cache
                        STARS_CACHE.addAll(paintings)
                        //now tell our adapter that it's data source has changed
                        adapter!!.addAll(STARS_CACHE, true)
                        //b.rv.setAdapter(mAdapter);
                        adapter!!.notifyDataSetChanged()
                        networkImages = getImageURLs(STARS_CACHE)
                        setupCarousel()
                        show(paintings.size.toString() + " bound from cache")
                    } else {
                        fetch("0", ITEMS_PER_PAGE)
                    }
                }
            )
        }
    }

Basically we are determining where we are fetching our data everytime the user visits this Listings Page. If our cache is dirty, and it will be only if we’ve made an upload, update or delete operation. If it is then we fetch our data from the server. Otherwise we simply attempt to load some from our hard disk cache. The attempt occurs asynchronously. If we do have some then we refresh our adapter, re-assign our network images and setup our carousel. If we haven’t any then we simply fetch data from the server.

That’s it for our Listings Activity. Be sure to check out the whole code.

Splash Activity

We will also have a splash page for our app.You can use it to render your logo, app title and app sub-title. You guys can remember we had written some animations at the beginning of the project. Well we will load our widget animations right within this activity, then apply them to our imageview and textviews:

    /**
     * Let's show our Splash animation using Animation class. We fade in our widgets.
     */
    private fun showSplashAnimation() {
        val dropAnim =AnimationUtils.loadAnimation(this, R.anim.drop)
        mLogo.startAnimation(dropAnim)
        val fadeInAnim = AnimationUtils.loadAnimation(this, R.anim.fade_in)
        mainTitle.startAnimation(fadeInAnim)
        subTitle.startAnimation(fadeInAnim)
    }

After half a second we will move to the dashboard page:

    /**
     * Let's go to our DashBoard after 1/2 second
     */
    private fun goToDashboard() {
        val t: Thread = object : Thread() {
            override fun run() {
                try {
                    sleep(500)
                    openActivity(this@SplashActivity, LoginActivity::class.java)
                    finish()
                    super.run()
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
        }
        t.start()
    }

Lesson – Upload Activity

This is the page we will be performing our CRUD operations. We will use methods we had already defined in our repository as well as ViewModel classes. Right here we simply do validation stuff then invoke those methods. We also handle capturing of images via camera as well as gallery and file picker.

So for example to capture images from camera or filepicker in this kotlin project, we will start by installing our ImagePicker library. We had done that earlier. Then we add the following method:

    override fun captureImage() {
        val intent = Intent(this, ImageSelectActivity::class.java)
        intent.putExtra(ImageSelectActivity.FLAG_COMPRESS, false) //default is true
        intent.putExtra(ImageSelectActivity.FLAG_CAMERA, true) //default is true
        intent.putExtra(ImageSelectActivity.FLAG_GALLERY, true) //default is true
        startActivityForResult(intent, 1213)
    }

Both Camera and Gallery are set to true.

Then simply listen to the onActivityResult() where we receive the captured image:

    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)
                if (filePath != null) {
                    chosenFile = File(filePath)
                    Picasso.get().load(chosenFile!!).error(R.drawable.image_not_found)
                        .into(b!!.topImageView)
                }
            }
        }
        resumedAfterImagePicker = true
    }

Here is how we upload our painting’s image and text to the server:

    /**
     * Upload Painting to the server
     */
    private fun upload(p: Painting) {
        remoteViewModel.upload(p, this.chosenFile!!)
            .observe(this, Observer { r: RequestCall? ->
                if (makeRequest(r, "STAR UPLOADING") == SUCCEEDED) {
                    CacheManager.CACHE_IS_DIRTY = true
                    b!!.painting= Painting()
                }
            })
    }

Here is how we update only text properties of our Painting in the server:

    private fun updateOnlyText(p: Painting) {
        remoteViewModel.updateOnlyText(p)
            .observe(this, Observer { r: RequestCall? ->
                if (makeRequest(r, "STAR UPDATE") == SUCCEEDED) {
                    CacheManager.CACHE_IS_DIRTY = true
                }
            })
    }

Here is how we update both images and text in the server:

    private fun update(p: Painting) {
        remoteViewModel.updateImageText(p, chosenFile!!)
            .observe(this, Observer { r: RequestCall? ->
                if (makeRequest(r, "STAR UPDATE") == SUCCEEDED) {
                    CacheManager.CACHE_IS_DIRTY = true
                }
            })
    }

Here is how delete both images and text from our server:

    private fun delete(p: Painting) {
        remoteViewModel.delete(p)
            .observe(this, Observer { r: RequestCall? ->
                if (makeRequest(r, "STAR DELETE") == SUCCEEDED) {
                    CacheManager.CACHE_IS_DIRTY = true
                    finish()
                }
            })
    }

That’s it. Again find the full code in the project.