If you want a template to create a full android application based on Kotlin or Java with Retrofit as HTTP Client and MySQL as the database, then this premium project is for you. We’ve carefully written this project to help students create an android crud app with several pages. Data is stored in MySQL database.

Tools Used

Here are the tools you will learn to use in this project

  1. Kotlin Programming Language.
  2. Similar project written in Java is also available.
  3. PHP as our server side language.
  4. MySQL as our database

Concepts You will Learn

Here are the concepts this project is designed to teach you.

  1. MVVM – Model View ViewModel design pattern in a full kotlin app. Also a Java project available.
  2. RETROFIT + PHP+ MySQL – Using as the HTTP client and MySQL as the database backend with PHP as the server side programming language. We see how to perform full CRUD operations.
  3. HARD DISK CACHING – How to achieve offline-first capability by caching data offline in the device.
  4. FULL APP – Creating a full android app with a multitude of pages.

Target Students

Download this project if you:

  1. Want to learn how to use Kotlin or Java to create a full app that can access a remotely hosted backend.
  2. If you want to master retrofit.
  3. If you want to learn MVVM(Model View ViewModel) with Kotlin or Java.
  4. If you want a template you can use to implement other types of projects that require CRUD capability.
  5. If you want to learn how to cache data in hard disk.
  6. If you want to learn how to design beautiful pages.
  7. If you want how to perform server side pagination of mysql data in an android app.

Step 1 – Preparing Data Object Classes

The very first step is to prepare our data object classes.

(a). Creating Our Data Object

We start by creating a data object class. This is the first class you will need to change if you want to customize this app. A data object class is a class that models the basic entity our app is about. For example in this case our app is sort of a biography app that shows users BigThinkers. You may customize this to be a movie,a product, a dog etc.

Here is the class in Kotlin:

class BigThinker : Serializable {
    @SerializedName("id")
    var id: String? = null
    @SerializedName("name")
    var name: String? = null
    @SerializedName("bio")
    var bio: String? = null
    @SerializedName("country")
    var country: String? = null
    @SerializedName("category")
    var category: String? = null
    @SerializedName("registration_date")
    var registrationDate: String? = null
    @SerializedName("period")
    var period: String? = null

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

Of course we have the class also in the java project:

public class BigThinker implements Serializable {
    @SerializedName("id")
    private String id;
    @SerializedName("name")
    private String name;
    @SerializedName("bio")
    private String bio;
    @SerializedName("country")
    private String country;
    @SerializedName("category")
    private String category;
    @SerializedName("registration_date")
    private String registrationDate;
    @SerializedName("period")
    private String period;

    //...

Note that I have ommitted the getters and setters in our java code. The java code is considerably longer than the kotlin code.

So in short our BigThinker will have the following properties

  1. Id – Autogenerated by MySQL as integer.
  2. Name – EditText
  3. Bio – Multiline EditText
  4. Country – Single Choice Dialog
  5. Category – Single Choice Dialog
  6. RegistrationDate – DatePickerDialog
  7. Period – EditText

(b). Modeling our server response

As we communicate with our server, we will be basically exchanging json data. The data that we download from the server will be in a particular format with particular fields. We want the data to be compatible so that Gson can automatically map our json data to our data object class. We will therefore need another data object class that will represent the json data we download from our server:

For Kotlin we have:

class ResponseModel {
    @SerializedName("bigthinkers")
    var result: MutableList<BigThinker?>? = null
    @SerializedName("code")
    var code: String? = null
    @SerializedName("message")
    var message: String? = null

}

And for Java:

public class ResponseModel {
    @SerializedName("bigthinkers")
    List<BigThinker> bigThinkers;
    @SerializedName("code")
    private String code;
    @SerializedName("message")
    private String message;

The string passed inside the @SerializedName() attribute has to be similar to the keys in the JSON data we download. Otherwise Malformed JSON exceptions will be thrown.

They fields are as follows

  1. Code – string – will represent our custom response code. For example we will use 1 to represent success and 2 to represent failure.
  2. Message – string – will represent the response message from the server. The message can be an error or success message.
  3. BigThinkers – List – This will represent a list of big thinkers we download from the server. In the PHP side it will be an array of objects.

NB/= These fields will have to be present for every response we pass back to the client. However we can pass an empty list if we were not downloading any data.

(c). Programmatically Representing Our Requests

We will be making HTTP requests to our server from our app. It therefore makes sense for us to define a simple data object to represent a given request. We represent the following properties of a given request:

  1. The status of request e.g in_progress,failed,succeded.
  2. The message associated with the request e.g failure message,success message,progress message.
  3. Data downloaded from the request.

In Kotlin:

class RequestCall {
    var status = 0
    var message: String? = null
    var bigThinkers: MutableList<BigThinker?>? = null
    var responseModel: ResponseModel? = null

}

In Java:

public class RequestCall {
    private int status;
    private String message;
    private List<BigThinker> bigThinkers;
    private ResponseModel responseModel;

 

2. Defining Our HTTP methods.

We will be making HTTP requests to our server using Retrofit2. Once we’ve defined our data objects as we did above, we now need to define abstract methods to represent our HTTP calls. We will be decorating these methods with attributes specifying the type of HTTP requests we will be making.

We start by defining our interface:

For Kotlin:

interface RestApi {

And for Java

public interface RestApi {
    //..Our methods

(a). Downloading all Data via HTTP GET

If you want to download all the rows at once then you can use this method:

For kotlin:

    @get:GET("index.php")
    val inventors: Call<ResponseModel?>?

And for Java:

    @GET("index.php")
    Call<ResponseModel> getInventors();

You can see we’ve decorated our method with @GET annotation. THis will imply we will be making a HTTP GET request against our index.php. This index.php is just a URL part and will be joined to our base url.

(b). Downloading/Searching Paginated Data

The method we had looked earlier allowed us to download all data at once. If you are having thousands of rows in your mysql database, it would consume considerable amount of users bandwith while making the app slow. Furthermore the user probably isn’t interested in viewing thousands of rows of data.

Let’s define a method we will use to download paginated data. Just a small chunk and as the user scrolls down the list we download more and more. This is more efficient and faster. This method can also be used to search data. This is because we will be sending some parameters to the server. Thus we can send the search term as well.

In Kotlin:

    @FormUrlEncoded
    @POST("index.php")
    fun search(
        @Field("action") action: String?,
        @Field("query") query: String?,
        @Field("start") start: String?,
        @Field("limit") limit: String?
    ): Call<ResponseModel?>?

And in Java:

    @FormUrlEncoded
    @POST("index.php")
    Call<ResponseModel> search(@Field("action") String action,
                               @Field("query") String query,
                               @Field("start") String start,
                               @Field("limit") String limit);

The fields include:

  1. action – The string to represent the action we are performing.
  2. query – The string to represent an optional search query.
  3. start – The string to represent the start row of our next page.
  4. limit – The string to represent the number of items we need for the next page.

We’ve used HTTP POST because it allows us easily send our parameters to the server.

Creating a New BigThinker

Well the below methods are also HTTP POST methods that will allow us to register or create a new bigThinker object:

In Kotlin:

    @FormUrlEncoded
    @POST("index.php")
    fun registerInventor(
        @Field("action") action: String?,
        @Field("name") name: String?,
        @Field("bio") bio: String?,
        @Field("country") country: String?,
        @Field("category") category: String?,
        @Field("registration_date") registration_date: String?,
        @Field("period") period: String?
    ): Call<ResponseModel?>?

In Java:

    @FormUrlEncoded
    @POST("index.php")
    Call<ResponseModel> registerInventor(@Field("action") String action,
                                         @Field("name") String name,
                                         @Field("bio") String bio,
                                         @Field("country") String country,
                                         @Field("category") String category,
                                         @Field("registration_date") String registration_date,
                                         @Field("period") String period
                                         );

Updating a New BigThinker

Then the following methods will allow us send a HTTP POST request that updates a bigThinker object. We send include an id as it will identify the row to be updated.

In Kotlin:

    @FormUrlEncoded
    @POST("index.php")
    fun updateInventor(
        @Field("action") action: String?,
        @Field("id") id: String?,
        @Field("name") name: String?,
        @Field("bio") bio: String?,
        @Field("country") country: String?,
        @Field("category") category: String?,
        @Field("registration_date") registration_date: String?,
        @Field("period") period: String?
    ): Call<ResponseModel?>?

In Java:

    @FormUrlEncoded
    @POST("index.php")
    Call<ResponseModel> updateInventor(@Field("action") String action,
                                       @Field("id") String id,
                                       @Field("name") String name,
                                       @Field("bio") String bio,
                                       @Field("country") String country,
                                       @Field("category") String category,
                                       @Field("registration_date") String registration_date,
                                       @Field("period") String period
    );

 

Delete a BigThinker

Lastly we have a method to allow us delete a bigThinker.

In Kotlin:

    @FormUrlEncoded
    @POST("index.php")
    fun deleteInventor(
        @Field("action") action: String?,
        @Field("id") id: String?
    ): Call<ResponseModel?>?

In Java:

    @FormUrlEncoded
    @POST("index.php")
    Call<ResponseModel> deleteInventor(@Field("action") String action,
                                       @Field("id") String id);

The id identifies the row to be deleted.

3. Performing our CRUD operations

In our repository class, we will define methods that will allow us to:

  1. C – Create a BigThinker
  2. R – Read our BigThinkers
  3. U – Update a BigThinker
  4. D – Delete a BigThinker

This repository class is our logic class.

For example here is the method that will allow us to register or create a bigthinker object:

In Kotlin:

    fun register(b: BigThinker): MutableLiveData<RequestCall> {
        val registerMutableLiveData = MutableLiveData<RequestCall>()
        val api =
            Utils.client?.create(
                RestApi::class.java
            )
        val insertData = api?.registerInventor(
            "REGISTER_BIGTHINKER",
            b.name, b.bio, b.country, b.category, b.registrationDate, b.period
        )
        val r = RequestCall()
        r.status = Constants.OPERATION_IN_PROGRESS
        r.message = "Registering BigThinker...Please wait.."
        registerMutableLiveData.value = r
        insertData!!.enqueue(object : Callback<ResponseModel?> {
            override fun onResponse(
                call: Call<ResponseModel?>,
                response: Response<ResponseModel?>
            ) {
                registerMutableLiveData.postValue(handleResponse(response, r))
            }

            override fun onFailure(
                call: Call<ResponseModel?>,
                t: Throwable
            ) {
                registerMutableLiveData.postValue(handleFailure(t.message, r))
            }
        })
        return registerMutableLiveData
    }

In Java:

    public MutableLiveData<RequestCall> register(BigThinker b) {
        MutableLiveData<RequestCall> registerMutableLiveData = new MutableLiveData<>();

        RestApi api = Utils.getClient().create(RestApi.class);
        Call<ResponseModel> insertData = api.registerInventor("REGISTER_BIGTHINKER",
                b.getName(), b.getBio(), b.getCountry(),b.getCategory(),b.getRegistrationDate(),b.getPeriod());

        RequestCall r = new RequestCall();
        r.setStatus(Constants.OPERATION_IN_PROGRESS);
        r.setMessage("Registering BigThinker...Please wait..");
        registerMutableLiveData.setValue(r);
        insertData.enqueue(new Callback<ResponseModel>() {
            @Override
            public void onResponse(Call<ResponseModel> call, Response<ResponseModel> response) {
                registerMutableLiveData.postValue(handleResponse(response,r));
            }

            @Override
            public void onFailure(Call<ResponseModel> call, Throwable t) {
                registerMutableLiveData.postValue(handleFailure(t.getMessage(),r));
            }
        });
        return registerMutableLiveData;
    }

4. Exposing Our CRUD functionality using the ViewModel class

ViewModel class is one of our simplest yet important classes, at least if we are to implement the MVVM design pattern. It will allow us expose functionalities we have already defined in our repository class to the UI. It keeps our repository class free from UI code. Our ViewModel class will be returning MutableLiveData objects. The RequestCall class will be instrumental as it will be the generic parameter of these MutableLiveData objects.

Here is our ViewModel class in the Kotlin project:

class BigThinkersViewModel(application: Application) : AndroidViewModel(application) {
    private val mRepository: BigThinkersRepository
    fun register(bigThinker: BigThinker?): MutableLiveData<RequestCall> {
        return mRepository.register(bigThinker!!)
    }

    fun update(bigThinker: BigThinker?): MutableLiveData<RequestCall> {
        return mRepository.update(bigThinker!!)
    }

    fun delete(bigThinker: BigThinker): MutableLiveData<RequestCall> {
        return mRepository.delete(bigThinker.id)
    }

    fun select(): MutableLiveData<RequestCall> {
        return mRepository.select()
    }

    fun search(
        query: String?,
        start: String?,
        limit: String?
    ): MutableLiveData<RequestCall> {
        return mRepository.search(query, start, limit)
    }

    init {
        mRepository = BigThinkersRepository()
    }
}

Here is our ViewModel class in the java project:

public class BigThinkersViewModel extends AndroidViewModel {
    private final BigThinkersRepository mRepository;

    public BigThinkersViewModel(@NonNull Application application) {
        super(application);
        mRepository = new BigThinkersRepository();
    }
    public MutableLiveData<RequestCall> register(BigThinker bigThinker){
        return mRepository.register(bigThinker);
    }
    public MutableLiveData<RequestCall> update(BigThinker bigThinker){
        return mRepository.update(bigThinker);
    }
    public MutableLiveData<RequestCall> delete(BigThinker bigThinker){
        return mRepository.delete(bigThinker.getId());
    }
    public MutableLiveData<RequestCall> select() {
        return mRepository.select();
    }

    public MutableLiveData<RequestCall> search(String query,String start,String limit) {
        return mRepository.search(query,start,limit);
    }

}

5. Defining application Constants

Constants are final fields whose values cannot change. They are immutable. Rather than clattering these important fields everywhere in our project, we can simply place them under one roof. That root is our Constants.kt or Constants.java:

For Kotlin we use an object class:

object Constants {
    const val DATE_FORMAT = "yyyy-MM-dd"
    const val BASE_URL = "https://camposha.info/php/user/bigthinkers/"
    //To use Localhost use your ip address e.g
    //public static  final String BASE_URL = "http://YOUR_IP_ADDRESS/php/user/bigthinkers/";
    //public static  final String BASE_URL = "http://192.168.43.91/php/user/bigthinkers/";
    const val OPERATION_IN_PROGRESS = 0
    const val OPERATION_COMPLETE_SUCCESS = 1
    const val OPERATION_COMPLETE_FAILURE = -1
    const val ERROR_STATE = -1
    const val PROGRESS_STATE = 0
    const val SUCCESS_STATE = 1
    const val CACHE_KEY = "CACHE_KEY"
}

For Java

public class Constants {
    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public  static  final String BASE_URL = "https://camposha.info/php/user/bigthinkers/";

    //To use Localhost use your ip address e.g
    //public static  final String BASE_URL = "http://YOUR_IP_ADDRESS/php/user/bigthinkers/";
    //public static  final String BASE_URL = "http://192.168.43.91/php/user/bigthinkers/";

    public static final int OPERATION_IN_PROGRESS = 0;
    public static final int OPERATION_COMPLETE_SUCCESS = 1;
    public static final int OPERATION_COMPLETE_FAILURE = -1;

    public static final int ERROR_STATE = -1;
    public static final int PROGRESS_STATE = 0;
    public static final int SUCCESS_STATE = 1;

    public static final String CACHE_KEY = "CACHE_KEY";
}
//end

6. Creating Re-usable Utility Methods

Well we will need some reusbale helper methods organized in one place. That place will be our Utils class.

(a). How to Instantiate Retrofit

You want to instantiate Retrofit and return it’s instance:

    val client: Retrofit?
        get() {
            if (retrofit == null) {
                retrofit = Retrofit.Builder()
                    .baseUrl(Constants.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
            }
            return retrofit
        }

(b). How to show a Toast message

You want a simple method you can reuse to show toast messages:

    @JvmStatic
    fun show(c: Context?, message: String?) {
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show()
    }

(c). How to validate EditTexts

You want one simple method you can re-use to validate a bunch of edittexts. The first three edittexts must not be empty.

    @JvmStatic
    fun validate(vararg editTexts: EditText): Boolean {
        val nameTxt = editTexts[0]
        val bioTxt = editTexts[1]
        val countryTxt = editTexts[2]
        if (nameTxt.text == null || nameTxt.text.toString().isEmpty()) {
            nameTxt.error = "Name is Required Please!"
            return false
        }
        if (bioTxt.text == null || bioTxt.text.toString().isEmpty()) {
            bioTxt.error = "Bio is Required Please!"
            return false
        }
        if (countryTxt.text == null || countryTxt.text.toString().isEmpty()) {
            countryTxt.error = "Country is Required Please!"
            return false
        }
        return true
    }

(d). How to Clear EditTexts

You want one simple method you can use to clear edittexts especially after using them to input data:

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

(e). How to Create a lovely material info dialog

You want a simple method that you can use to show material info dialog anywhere in the app, be it in a fragment of an activity:

    @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.m_info)
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton(
                "Relax"
            ) { v: View? -> }
            .setNeutralButton(
                "Go Home"
            ) { v: View? ->
                openActivity(
                    activity,
                    DashboardActivity::class.java
                )
            }
            .setNegativeButton(
                "Go Back"
            ) { v: View? -> activity.finish() }
            .show()
    }

 

(f). How to select date from MaterialDatePicker

You want a simple method that allows you to show a material datepicker dialog and set the selected date in an edittext you supply.

    @JvmStatic
    fun selectDate(a: AppCompatActivity, dateTxt: EditText) {
        dateTxt.setOnClickListener { v: View? ->
            val dialog =
                DatePickerFragmentDialog.newInstance { view: DatePickerFragmentDialog?, year: Int, monthOfYear: Int, dayOfMonth: Int ->
                    val month: String
                    val day: String
                    var theMonth = monthOfYear
                    theMonth++
                    month = if (theMonth < 10) {
                        "0$theMonth"
                    } else {
                        theMonth.toString()
                    }
                    day = if (dayOfMonth < 10) {
                        "0$dayOfMonth"
                    } else {
                        dayOfMonth.toString()
                    }
                    dateTxt.setText("$year-$month-$day")
                }
            dialog.show(a.supportFragmentManager, "DATE_PICKER")
        }
    }