Sportigan App : Multi-Admin Sports App – Retrofit MySQL Multipart Phone+PIN Authentication

User Management System
Learn Kotlin, Retrofit, MVVM and MySQL using this all-in-one app. It is designed to be beginner friendly.

Am an ocassional sports fan. I used to be an avid fan when I was younger. I was into European sports and my team used to be Manchester United. Those were the Fergie days. Yesterday I was with a couple of friends watching Man United beat Chelsea 2-0 at stamford brigde in a highly explosive match. I fully enjoyed it. Then I thought what about I create an app that can allow multiple users from all over the world post news and articles. What about if I create a sort of a blogging app that allows users to post news about sports. I then would be able to follow updates of various events happening in the world of sports.

So I started writing the features that the app would need. Here are the features I decided to include:

  1. MULTIPLE USERS - Users both registered and non-registered can use the app. The non-registered users can only view posts but can not edit or publish news. Registered Users on the other hand can publish news. They can also edit only the news they published. They can view other peoples news but cannot edit them.
  2. MYSQL DATABASE - Articles will be stored in mysql database in the server. Users will also be stored in mysql database as well. However to reduce complexity, tags and categories will be stored in a PHP array rather than mysql database. Tags and categories are too simple and don't require a whole database table. The admin is the only one who can edit/delete/add these tags/categories as he/she has access to the server.
  3. PHP - PHP will be used to interact with the database. It will receieve HTTP requests sent from the client, then perform an operation and return a response. We will use Redbean PHP as our ORM. RedbeanPHP makes working with databases from PHP easier. It allows us utilize Object Oriented Programming paradigm in our PHP code to perform various operations we want. It's super clean, reliable and stable.
  4. PHONE AUTHENTICATION - Who needs an email address when we already have phone numbers. I decided to implement (phone number + pin) form of authentication. It will make our users' work much much easier. They don't even need to type their phone numbers. We can pick this programmatically with the right permissions. Then we ask them to enter a pin. That's it. If they are not yet registered with us then we auto-register them, then auto-log them in. If they are already registered we simply sign them in. No complex signups,login process.
  5. RETROFIT MULTIPART UPLOAD - We will be performing efficient multipart upload of images using Retrofit. The images will be stored in the server in a folder. Their path will however be stored in mysql database.
  6. CRUD OPERATIONS - Registered users will be able to publish a post, edi,delete, read. These posts will be comprising of several data types like strings, dates and images.
  7. CAMERA - Registered users will be able to capture images directly from the camera and upload them alongside the text aspects of the article. Users can also pick images from gallery or file manager.
  8. RUNTIME PERMISSIONS - Runtime permissions are handled. To capture/pick images we have a library that automatically handles this. Then we also handle runtime permissions for reading the phone state. Users can accept/deny these permissions at runtime.
  9. SERVER SIDE PAGINATION - Because this a multi-admin app, we can expect huge amounts of articles over time. We don't want to be downloading all these articles at once. That would be costly in terms of users bandwith and memory consumption. Instead we want to be downloading only like 5-10 articles at a time. We automatically load more as the once scrolls downn. Thus we not only utilize miniscule bandwidth but also make the app faster due to low memory consumption.
    11.IMAGE SLIDER/CAROUSEL - We will include a beautiful image slider at the top of our listings page. This auto slides the images that have so far been downloaded in our articles. Users can optionally swipe through the images.
  10. BEAUTIFUL YET SIMPLE DASHBOARD - We want to include a central activity that can be used for navigation within the app. The dashboard will have cardviews that when clicked take us to appropriate pages.
  11. ONE TIME LOGIN ONLY - Users will login only once in the app. Then their login credentials are stored securely in SharedPreferences. Thus the next time or day when the user comes back to the app, he/she will be autologged in. He/She won't have to be typing pin every time. Of course users can logout. In that case their details will be removed from SharedPreferences. Then the next time they come to the app, they will need to enter their pin.
  12. READABLE DETAILS TEXT - Users will come to the app to either post or read articles. Hence the articles need to be readable and minimalistic. That's exactly how we've designed the details page. We have the CollapsingToolbarLayout class to assist with rendering our header image.

How to Create a Sports News App

The purpose of the app is to help students learn how to create a sports app. In the process they will learn how to create a full native android application using Kotlin or Java. They will learn how to interact with mysql database to store/fetch/update/delete articles and users. And how use phone number + pin combination instead of email+password.

Let's start.

(a). Dependencies

We will use several third party libraries to allow us achieve some important functionalities rather than having to re-invent the wheel. Here are some of our most important dependencies:

  1. Retrofit2 - We use it to make our HTTP requests. It works alongside Gson to make our work easier.
  2. Picasso - We use it to load our images from the server. T will also cache these images.
  3. Calligraphy - We want to use custom fonts downloaded from the web. We can use Callighraphy to achieve this.

NB/= Make sure that jitpack is added a repository in the root level build.gradle:

You will find many many more dependencies included in the project. You can ommit some of them, but then you will have to re-edit your code to remove some of the features included in the app.

allprojects {
    repositories {
        maven { url "" }

(b). Permissions

Some of the capabilities we use in the project require user's permission. They include:

  1. Ability to connect to network. This requires internet permission.
  2. Ability to capture/pick images - This requires external storage-read permission.
  3. Ability to read user's phone number and name - These require the read-phon-state permission.

We will implement runtime permissions for the last two capabilities in the above list.

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

Here is how we check for phone -read permission for example at runtime:

String neededPermission = Manifest.permission.READ_PHONE_STATE;


    private boolean checkPermission(String permission) {
        if (Build.VERSION.SDK_INT >= 23) {
            int result = ContextCompat.checkSelfPermission(AuthActivity.this, permission);
            return result == PackageManager.PERMISSION_GRANTED;
        } else {
            return true;

Here is how we request user for permission:

    private void requestPermission(String permission) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(AuthActivity.this, permission)) {
            Toast.makeText(c, "Phone state permission allows us to get phone number. Please allow it for additional functionality.", Toast.LENGTH_LONG).show();
        ActivityCompat.requestPermissions(AuthActivity.this, new String[]{permission}, PERMISSION_REQUEST_CODE);

The here's how we handle the result of the request:

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Phone number: " + getPhone());
                    show("PERMISSION GRANTED: Phone Num is: " + getPhone());
                } else {
                    show("Permission Denied. We can't get phone number.");

Creating Our Data Object

Our data object is a News object. If you want to customize this app, then this is the first class you will need to change. It defines our basic entities, the properties which will make up a single article.

Here are the properties we will representing in this project:

  1. Title - String - EditText
  2. Content - String - MultiLine EditText
  3. Tags - String - Single Choice Dialog
  4. Category - String - Single Choice Dialog
  5. Status - String - Single Choice dialog
  6. DatePublished - String - MaterialDatePicker
  7. Featured Image - File - ImageView.
  8. Author Id - String - Author fo whoever published the news.

public class News implements Serializable {
    private String id;
    private String title;
    private String content;
    private String tags;
    private String category;
    private String status;
    private String datePublished;
    private String featuredImg;
    private String authorId;


Creating Our User Object

We will also have a data class called User. This class will represent a single user. Here are the properties of our User objects:

  1. Id - String - Autogenerated by MySQL database.
  2. Name - String - Picked automatically from device
  3. Phone Number - String - Picked automatically from device.
  4. Pin - 4 digit number but saved as string - User enters using a beautiful pinView.
  5. Status - String - Admin can assign you a status at the server side.

public class User implements Serializable {
    private String id;
    private String name;
    private String phoneNumber;
    private String pin;
    private String status;

Modeling Our Server Response

Our app will exchange JSON data with PHP. We will use Gson to map our json data to data object classes. We have created a simple class called ResponseModel. Our server response will be mapped to this class.

public class ResponseModel {
     * Our ResponseModel attributes
    private String code;
    private String message;
    private List<News> news;
    private List<String> categories;
    private List<String> tags;

Now this doens't mean that we will be downloading all these attributes at once. Most of them will actually be empty or null depdending on the request we are making.


Defining Our HTTP Methods

We will be communicating to the server via HTTP requests. We will define our own abstract methods that will utilize some of the standard HTTP methods. We will define these methods in an interface:

public interface RestApi {

(a). Downloading All Rows

The below method will allow us make a HTTP GET request against our server, in the process downloading all data.

    Call<ResponseModel> retrieve();

Use the method if you don't want to do pagination, and just want to download all rows from the server.

(b). Search and Pagination

The below method will allow us to fetch data based on a filter we apply as well as pagination parameters. We are basically searching and paginating the search results:

    Call<ResponseModel> search(@Field("action") String action,
                               @Field("query") String query,
                               @Field("start") String start,
                               @Field("limit") String limit);

Here are the parameters:

  1. action - The action we will perform. Remember we will be re-using the same method, so the action differentiates the operation we are performing.
  2. query - A string we can use to filter. If you don't want to search you pass empty string.
  3. start - Start of the pagination
  4. limit - The number of items you want for you page.

We are sending the above parameters using a HTTP POST request.

(c). Uploading Image and Texts

Here is the definition of the method we are using to upload our data:

    Call<ResponseModel> upload(
            @Part("action") RequestBody action,
            @Part("title") RequestBody title,
            @Part("content") RequestBody content,
            @Part("tags") RequestBody tags,
            @Part("category") RequestBody category,
            @Part("status") RequestBody status,
            @Part("date_published") RequestBody datePublished,
            @Part("author_id") RequestBody author_id,
            @Part MultipartBody.Part featuredImg);

We are performing a Multipart request, that's why we have annotated the method using the @Multipart decoration. It's still a HTTP POST request, and we are making it against our index.php file. The retrofit2.Call object is being returned and we can enqueue it to get a ResponseModel object. Here are the parameters sent to the server:

  1. action - To identify this HTTP POST request from the other HTTP POST requests we will be performing.
  2. title - The title of the news article.
  3. content - The content of the news article.
  4. tags - The tags of the news article.
  5. category - The category of the news article.
  6. status - The status of the news e.g published,draft etc.
  7. date_published - The date the article was published.
  8. author_id - the id of the publisher.
  9. featuredImg - The image file we are sending to the server.

(d). Updating Image and Text

The following method will allow us to update a news article, including both image and text.

    Call<ResponseModel> update(
            @Part("action") RequestBody action,
            @Part("id") RequestBody id,
            @Part("title") RequestBody title,
            @Part("content") RequestBody content,
            @Part("tags") RequestBody tags,
            @Part("category") RequestBody category,
            @Part("status") RequestBody status,
            @Part("date_published") RequestBody datePublished,
            @Part("author_id") RequestBody author_id,
            @Part MultipartBody.Part featuredImg);

The id identifies the row we are updating. This method will autotamtically be used if the user clicks the update button after he/she has changed atleast the featured image of the article.

(d). Update Only Text

Sometimes the user will change only text but not the image. In that case it would be more efficiently to just update text using a normal HTTP POST request without doing a multipart upload. There is no need to re-upload the same image.

    Call<ResponseModel> updateOnlyText(@Field("action") String action,
                                       @Field("id") String id,
                                       @Field("title") String title,
                                       @Field("content") String content,
                                       @Field("tags") String tags,
                                       @Field("category") String category,
                                       @Field("status") String status,
                                       @Field("date_published") String datePublished,
                                       @Field("author_id") String authorId);

(e). Delete Image and Text

The following method will be used to delete our image and text from our mysql database.

    Call<ResponseModel> delete(@Field("action") String action, @Field("id") String id);

The action identifies the operation we are performing. The id identifies the row we are deleting.

(f). Getting Taxonomies

We have two taxonomies:

  1. Categories
  2. Tags

The following method will allow us to fetch both at once.

    Call<ResponseModel> getTaxonomies(@Field("action") String action);

Because our taxonomies are just some simple strings, we won't store them in a database table. We can simply hold them in an array and return them when needed. The admin can still update the taxonomies even after publishig the app since he has access to the server. However it also makes our request super fast as we will perform it quitely in the background without notifying the user. Then we will cacche those taxonomies in a static array and bind the array to our chooser dialogs.

(e). Creating/Authenticate an Account

User account details will also be stored in the mysql database. The below method will be used to authenticate user

    Call<ResponseModel> createAccount(@Field("action") String action,
                                      @Field("name") String name,
                                      @Field("phone_number") String phoneNumber,
                                      @Field("pin") String pin,
                                      @Field("status") String status);

Here are the parameters:

  1. action - To identify the operation. it can either be creating an account or signing in the user.
  2. name - The name of the user.
  3. phone_number - The phone number of the user.
  4. pin - The user's pin number
  5. status - The status of the user, whether active or not-active.

Note that we will use the above method for:

  1. Creating a New account if one doesn't exist.
  2. Signing the User in an account with the same phone number and pin already exists.

Defining Application Constants

Constants are fields that don't change. They are final. Well we need such type of fields in our project.

public class Constants {

    //supply your ip address. Type ipconfig while connected to internet to get your
    //ip address in cmd. Watch video for more details. For example below I have used my ip address
    public   static  final String BASE_URL = "";
    public   static  final String IMAGES_BASE_URL = "";
    //private  static  final String base_url = "//";
    //public static final String BASE_URL = "//";
    //private  static  final String base_url = "";
    //if you use genymotion emulator then use below
    //private  static  final String base_url = "";
    //public static final String IMAGES_BASE_URL = "//";

    public static final String[] LOCAL_CATEGORIES = {"SOCCER", "BASKETBALL", "TENNIS", "RUGBY", "CRICKET",

    public static final String[] LOCAL_TAGS = {"EPL", "LA-LIGA", "SERIE A", "BUNDESLIGA", "OLYMPICS",
            "WORLD CUP", "OTHER"};
    public static final String[] POST_STATUS = {"PUBLISHED_PUBLIC", "PUBLISHED_PRIVATE", "DRAFT"};


The LOCAL_CATEGORIES and LOCAL_TAGS will only be used if we couldn't download the categories and tags from the server. We will attempt to do this in the bacground in our dashboard page as well as in our CRUD page.


Caching Data in Memory

When the user opens the app, he can visit many pages several times without closing the app.Let's call this a session. In one session we want to ideally make a single request to the server, unless the user is updating several updates. So we want to cache data temporarily in memory so that we won't have to be connecting to the server for every page the user visits.

We will therefore define a simple class called CacheManager which will statically hold variables we can use. These static variables will be available across a whole session without changing. Only their content can change.

public class CacheManager {
    public static List<News> NEWS_MEM_CACHE =new ArrayList<>();
    public static Boolean MEM_CACHE_DIRTY = true;
    public static List<String> CATEGORIES =new ArrayList<>();
    public static List<String> TAGS =new ArrayList<>();
    public static User CURRENT_USER = null;

Saving Logged In User Locally using SharedPreferences

SharedPreferences is a class that can allow us store key-value pairs in our device locally and securely. This is perfect for storing a Logged In user. This therefore allows us to automatically login a user without connecting to the server.

The user details are stored locally as key-value pairs. We can logout the user and delete those details from the sharedpreferences. In that case the user will have to login again. Details are only stored on successful login.

So we start by creating a class we can use:

public class PrefUtils {

Then a private method to initialize for us our SharedPreferences:

    private static SharedPreferences getPreferences(Context context){
        return context.getSharedPreferences("CURRENT_USER_DETAILS", Context.MODE_PRIVATE);

The method is private since we only use it within this class.

Then to save to SharedPreferences, we start by defining a method that receives the User as well as the Context:

    public static void save(Context context, User user) {

Then get the sharedpreferences editor:

        SharedPreferences.Editor editor = getPreferences(context).edit();

Then put our details in the editor:

        editor.putString("ID", user.getId());
        editor.putString("NAME", user.getName());
        editor.putString("PHONE_NUMBER", user.getPhoneNumber());
        editor.putString("PIN", user.getPin());
        editor.putString("STATUS", user.getStatus());

Then invoke either the apply() or commit() method. The apply() is recommended as it allows us to perform our writes in the bacground thread:


We can also load a user from the SharedPreferences easily:

    public static User load(Context context) {
        SharedPreferences sp = getPreferences(context);
            return null;
        User user=new User();
        return user;

If the user isn't there we simply return null.

If the user logs out, we will need to delete the user from our sharedpreferences. The below method allos us to delete:

    public static void delete(Context context) {
        SharedPreferences.Editor editor = getPreferences(context).edit();




Permission Management

Because this a multi-user multi-admin app, we need a way to handle roles and permissions. Permissions limit users in their capabilities based on the account type they have. For example one user cannot delete another user's posts. He can view them but can't edit or delete them. But of course he can edit or delete his own posts.

Let's create a method that will facilate this for us:

public class PermissionManager {

The method below will allow us to check if a user is logged in or not:

    public static boolean isLoggedIn() {
        return CURRENT_USER != null && !Utils.notNull(CURRENT_USER.getId()).isEmpty();

The below method will allow us determine if a user can create a news article or not:

    public static boolean canCreateNews() {
        if (!isLoggedIn()) return false;
        return true;

The below method will allow us determine if a user can edit or delete a news article:

    public static boolean canEditOrDeleteNews(News news) {
        if (!isLoggedIn()) return false;
        if (CURRENT_USER.getId().equalsIgnoreCase(news.getAuthorId())) {
            return true;
        return false;

Re-usable How-To Utility Methods.

We will hold these methods in our Utils class:

public class Utils {

(a). How To Instantiate Retrofit

Retrofit is our HTTP client. To make our requests we will use it. We need one method to use it to for instantiation:

    private static Retrofit retrofit;

     * This method will return us our Retrofit instance which we can use to initiate HTTP
     * calls.
    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()

        return retrofit;

(b). How to Show Toast Messages

THis method will allow us show Toast messages throughout all activities

    public static void show(Context c, String message) {
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show();

(c). How to Open an Activity

This utility method will allow us open any activity.

    public static void openActivity(Context c, Class clazz) {
        Intent intent = new Intent(c, clazz);

(d). How to Send a News Item Across Activities and Receive it

The following method will allow us send a news item to another activity:

    public static void sendNewsToActivity(Context c, News news,
                                          Class clazz) {
        Intent i = new Intent(c, clazz);
        i.putExtra("NEWS_KEY", news);

This method will allow us receive a serialized news, deserialize it and return it,.

    public static News receiveNews(Intent intent, Context c) {
        try {
            return (News) intent.getSerializableExtra("NEWS_KEY");
        } catch (Exception e) {
            show(c, "RECEIVING-STAR ERROR: " + e.getMessage());
        return null;

(e). How to obtain a Value from an EditText

Because we will be extracting typed values from our editttext, we cannot not only make this extraction process easier but also safer, avoiding null pointer exceptions.

Here is the method we will use:

    public static String valOf(EditText editText) {
        if (editText == null) {
            return "";
        return editText.getText().toString();

(f). How to Safely Return a Value as a string

We will be obtaining values from various methods, most nably our getter methods. It would be much safer if we can have a method that can make this process safer. This method will return a string version of an object. If the object is null it will return an empty string.

    public static String notNull(Object o) {
        if (o != null) {
            return o.toString();
        return "";

(g). How to Create an Info Dialog with Buttons

We will use this to show not only information, messages, warnings but also error messages. The user has to manually dismiss the dialog unlike the Toast messages. The advantage of that is that we are sure the user will have read the text shown there.

    public static void showInfoDialog(final AppCompatActivity activity, String title,
                                      String message) {
        new LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
                .setPositiveButton("Fine", v -> {
                .setNeutralButton("Go Home", v -> openActivity(activity, DashboardActivity.class))
                .setNegativeButton("Go Back", v -> activity.finish())

(h). How to Select a Post/News/Article Status

The statuses had already been defined in our Constants class. We want to show a single choice dialog that allows the user to pick one status.

    public static void selectStatus(AppCompatActivity activity, final EditText galaxyTxt) {

        ArrayAdapter<String> adapter = new ArrayAdapter<>(activity,
        new LovelyChoiceDialog(activity)
                .setTitle("Status Picker")
                .setMessage("Select the Status of this News.")
                .setItems(adapter, (position, item) -> galaxyTxt.setText(item))


(i). How to Select Post/News/Article Category

The Categories will be fetched from the server. If the user doesn't have internet connectivity, then we will use some predefined categories in our Constants class.

    public static void selectCategory(AppCompatActivity activity, final EditText galaxyTxt) {
        List<String> categories = Arrays.asList(LOCAL_CATEGORIES);
        if (CacheManager.CATEGORIES.size() > 0) {
            categories = CacheManager.CATEGORIES;
        ArrayAdapter<String> adapter = new ArrayAdapter<>(activity,
        new LovelyChoiceDialog(activity)
                .setTitle("Categories Picker")
                .setMessage("Select the Category of this News.")
                .setItems(adapter, (position, item) -> galaxyTxt.setText(item))



(j). How to Load Image From Network

We will use Picasso to download our images from network and show them in the appropriate imageview. If an error occurs we will show a replacement image.

    public static void loadImageFromNetwork(String imageURL, int fallBackImage,
                                            ImageView imageView) {
        if (imageURL != null && !(imageURL.isEmpty())) {
        } else {

(k). How Extract Image URLS from a List of News Items

The idea is that we want to extract these URLs so that we can show them in our beautiful image slider/carousel.

    public static String[] getImageURLs(List<News> list) {
        String[] imageURLs = new String[list.size()];

        int i = 0;
        for (News news : list) {
            imageURLs[i] = Constants.IMAGES_BASE_URL + news.getFeaturedImg();
        return imageURLs;

(l). How to Check if a News Item already Exists in our Memory cache

This is important as we are doing load more pagination, downloading data as the user scrolls. We don't want duplicates in our cache.

    public static boolean alreadyExists(News news) {
        for (News n : NEWS_MEM_CACHE) {
            if (n.getId().equalsIgnoreCase(news.getId())) {
                return true;
        return false;

And more and more.


The project will be updated continously. If you purchase it we will be emailing these updates and bug fixes. We will be adding more and more features. You will also get Kotlin version for free.

Learn Android Retrofit using our course

Android MySQL Retrofit2 Multipart CRUD,Search,Pagination rating

When I was a 2nd year Software Engineering student, I buillt a now defunct online tool called Camposha(from Campus Share) using my then favorite language C#(ASP.NET) to compete OLX in my country(Kenya). The idea was to target campus students in Kenya. I got a few hundred signups but competing OLX proved too daunting. I decided to focus on my studies, learning other languages like Java,Python,Kotlin etc while meanwhile publishing tutorials at my YouTube Channel ProgrammingWizards TV which led to this site( Say hello or post me a suggestion: . Follow me below; Github , and on my channel: ProgrammingWizards TV

We will be happy to hear your thoughts

Leave a reply

31 − twenty one =

Reset Password
Compare items
  • Total (0)
Shopping cart