Download image Android – External storage [Java]

Learn how to download image in your Android app. The download images will be saved in external storage using Java.

Demo

First we will create a download icon. Goto your drawable folder and create an XML file named “ic_file_download.xml” and paste the following code in it:

ic_file_download.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector>

Now we need to create a layout for dialog which will be opened. So create a file named “image_detail_dialog.xml” and paste the following code in it:

image_detail_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#cccccc"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:padding="10dp">

        <ImageView
            android:id="@+id/download"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:src="@drawable/ic_file_download"
            android:tint="#ffffff" />
    </RelativeLayout>

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true" />
</LinearLayout>

This will create a layout where you can see the download button on top right with black background color. And an image will be displayed in the center of screen with full width and height.

Now we need to show this dialog to the user. For that, we need to create a separate class which extends from Dialog. But, to save the image in SD Card or phone memory, we need to get external file write permission from user. So that code will only be run in Activity class.

So we need to create an interface first:

DownloadAttachmentListener.java

public interface DownloadAttachmentListener {
    void onDownloadClick(String imagePath);
    void onDownloaded();
}

It has 2 functions, 1 will be called when user requested to download the image. 2nd will be called when the image is fully downloaded and saved in SD Card or phone memory.

Now in your manifest file, set the permission to read and write the external storage:

AndroidManifest.xml

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

Downloading the file from server is an asynchronous task and can only be done in a class which extends from AsyncTask. So create a new class named “DownloadImageTask.java” and paste the following code in it:

DownloadImageTask.java

import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;

public class DownloadImageTask extends AsyncTask<Void, Void, Void> {
    private String imagePath, fileName;
    private Context context;
    private DownloadAttachmentListener downloadAttachmentListener;

    public DownloadImageTask(Context context, String imagePath, String fileName, DownloadAttachmentListener downloadAttachmentListener) {
        this.context = context;
        this.imagePath = imagePath;
        this.fileName = fileName;
        this.downloadAttachmentListener = downloadAttachmentListener;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        try {
            File root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
            File directoryPath = new File(root.getAbsolutePath() + "/" + context.getString(R.string.app_name));
            if (!directoryPath.exists()) {
                directoryPath.mkdir();
            }

            File cachePath = new File(directoryPath + "/" + fileName + ".jpg");
            cachePath.createNewFile();
            byte[] buffer = new byte[1024];
            int bufferLength;

            URL url = new URL(imagePath);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setDoOutput(false);
            urlConnection.connect();

            InputStream inputStream = urlConnection.getInputStream();
            FileOutputStream fileOutput = new FileOutputStream(cachePath);
            while ((bufferLength = inputStream.read(buffer)) > 0) {
                fileOutput.write(buffer, 0, bufferLength);
            }

            fileOutput.write(buffer);
            fileOutput.close();
            inputStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        if (downloadAttachmentListener != null) {
            downloadAttachmentListener.onDownloaded();
        }
    }
}

It is receiving image path, file name which will be set in SD Card / phone memory and download attachment listener, which will call the function “onDownloaded()” when the image is fully downloaded and saved in user’s phone.

Now create this download attachment listener interface’s instance in your activity:

private DownloadAttachmentListener downloadAttachmentListener = new DownloadAttachmentListener() {
    @Override
    public void onDownloadClick(String imagePath) {
        // Check if we have write permission
        int permission = ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permission != PackageManager.PERMISSION_GRANTED) {
            // We don't have permission so prompt the user
            ActivityCompat.requestPermissions(
                    YourActivity.this,
                    new String[]{
                            Manifest.permission.READ_EXTERNAL_STORAGE,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    100
            );
        } else {
            String fileName = new Date().getTime();
            new DownloadImageTask(getApplicationContext(), imagePath, fileName, downloadAttachmentListener).execute();
        }
    }

    @Override
    public void onDownloaded() {
        Toast.makeText(getApplicationContext(), "File has been saved at Phone\\Pictures\\" + getResources().getString(R.string.app_name), Toast.LENGTH_LONG).show();
    }
};

Now create a dialog which will display the layout we created above and when the dialog icon is clicked, we will call this “onDownloadClick” function:

PictureMessageDialog.java

package com.scp.app.scp_chatapp.custom;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.scp.app.scp_chatapp.R;
import com.scp.app.scp_chatapp.interfaces.DownloadAttachmentListener;
import com.scp.app.scp_chatapp.models.MessageModel;
import com.scp.app.scp_chatapp.utils.Utility;

public class PictureMessageDialog extends Dialog {
    private Context context;
    private String imagePath;

    private ImageView image, download;
    private DownloadAttachmentListener downloadAttachmentListener;

    public PictureMessageDialog setDownloadAttachmentListener(DownloadAttachmentListener downloadAttachmentListener) {
        this.downloadAttachmentListener = downloadAttachmentListener;
        return this;
    }

    public PictureMessageDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
        this.context = context;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getWindow() != null) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
        setContentView(R.layout.image_detail_dialog);

        image = findViewById(R.id.image);
        download = findViewById(R.id.download);
        Glide.with(context)
                .load(imagePath)
                .asBitmap()
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .skipMemoryCache(true)
                .into(image);

        download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                downloadAttachmentListener.onDownloadClick(messageModel);
            }
        });
    }

    public PictureMessageDialog setImagePath(String imagePath) {
        this.imagePath = imagePath;
        return this;
    }
}

This will create a dialog class and we are setting this to full screen width and height.

Now we just need to show the dialog class we created earlier and pass this interface instance, along with image path:

new PictureMessageDialog(YourActivity.this, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
    .setDownloadAttachmentListener(downloadAttachmentListener)
    .setImagePath("https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/pathfinder_full.jpg")
    .show();

You can call this function wherever you want to display the image in full screen. Once displayed, you will see a download icon on top right with black background. When that icon is clicked, you will be asked to give write permissions to your external storage. Once write external storage permission is granted, file will be saved in your phone memory, inside pictures folder. Following will be the path:

Phone/Pictures/{your_app_name}/filename.jpg

After download, you can see your image in android external storage.

Learn how to upload image from Android gallery to PHP server.

Profile update with picture – Android, Java, PHP, and MySQL

Get data from PHP and MySQL in Android Java

In this tutorial, we will teach you how you can get data from database using PHP and MySQL in Android Java. We will be using Volley for sending HTTP request.

Following will be the structure of users table from where we will fetch data. You can add more fields as per your need. Important thing here is auto-increment primary key ID:

--
-- Table structure for table `users`
--

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for table `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

We will be sending an HTTP request to a PHP server, here it is it’s code:

get-data.php

<?php

    $userId = $_POST["userId"];
    $conn = mysqli_connect("localhost", "root", "root", "tutorials");

    $sql = "SELECT * FROM `users` WHERE `id` = '" . $userId . "'";
    $result = mysqli_query($conn, $sql);

    if (mysqli_num_rows($result) > 0)
    {
        $row = mysqli_fetch_object($result);

        echo json_encode(array(
            "status" => "success",
            "message" => "Successfully logged in",
            "user" => $row
        ));
    }
    else
    {
        echo json_encode(array(
            "status" => "error",
            "message" => "User not found"
        ));
    }
    exit();

?>
  1. First we are getting userId which will be sent by android app using POST method.
  2. Then we are connecting with MySQL database using host, username, password and database name respectively.
  3. Then we are fetching data from users table using his ID.
  4. Check if record does not exists, then simply send an error message as response. All responses must be in JSON string.
  5. If exists, then simply fetch that row, and send in response. That will be received by android.

Now include Volley in your android project’s app/build.gradle:

dependencies {
    ...

    // To convert JSON string into Java class object
    implementation 'com.google.code.gson:gson:2.8.5'

    // To send an HTTP request to PHP server
    implementation 'com.android.volley:volley:1.1.1'
}

First, we need to define the structure of data received from server. Create a class in your android project’s java package named UserModel.java, you can add more fields as per need:

public class UserModel {

    private String id;
    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Now simply send an HTTP request using Volley and convert the response into this UserModel class:

String url = "http://localhost/tutorials/get-data.php";
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {

                try {
                    JSONObject responseJson = new JSONObject(response);
                    String status = responseJson.getString("status");
                    String message = responseJson.getString("message");

                    if (status.contains("error")) {
                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                    } else {
                        UserModel userModel = new Gson().fromJson(responseJson.getString("user"), UserModel.class);

                        // do whatever you want with userModel
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        error.printStackTrace();
        Toast.makeText(getApplicationContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
    }
}) {
    @Override
    protected Map<String, String> getParams() {
        Map<String, String> params = new HashMap<>();
        params.put("userId", "1");

        return params;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String> params = new HashMap<>();
        params.put("Content-Type", "application/x-www-form-urlencoded");
        return params;
    }
};

// To prevent timeout error
stringRequest.setRetryPolicy(new DefaultRetryPolicy(50000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

// Add the request to the RequestQueue.
stringRequest.setShouldCache(false);
queue.add(stringRequest);
  1. We are sending request to get-data.php file.
  2. The method should be POST. We are sending userId parameter and sending the value in it. You might be storing this ID in shared preference. Learn how to save values in Shared Preference from here.
  3. When the response is received, we are simply converting that into JSON. Getting status and message strings.
  4. If the status is “error”, then simply display a message in Toast.
  5. Then convert the “user” key into UserModel class object and save it in object named userModel.
  6. Now you can use that object however you want.

Now that you have learned to fetch data from database using PHP and MySQL and convert that into Android Java class object, learn how to save the data from android to PHP and MySQL.

Upload android gallery image to PHP, MySQL – Java

In this article, we will show you how you can upload image from your android gallery to PHP server.

Introduction

We will create a profile page in an android application where we wil display the user profile picture, name and a submit button. You can also display other fields as per your project. And when the form is submitted, we will send an HTTP request from android to PHP server that will save the user’s selected picture in PHP server and save its path in MySQL database along with its ID.

We will also discuss how you can display an image from a live server in an android application. We will be displaying the user profile image in circular form as in the screenshot.

In your app/build.gradle, make sure to add Volley, Glide and CircleImageView dependency:

implementation 'com.android.volley:volley:1.1.1'

// Used to display image from live HTTP server in imageview
implementation 'com.github.bumptech.glide:glide:3.7.0'

// To display image in circular form
implementation 'de.hdodenhof:circleimageview:2.2.0'

MySQL table structure

--
-- Table structure for table `users`
--

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` text NOT NULL,
  `picture` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for table `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

We will have a “picture” name column in your user’s table. ID will be the primary unique key used to identify the user.

Now come to the android part. First, we will be creating a layout for the profile page. To keep the things simple, we are using name, and picture in the profile page:

layout/activity_profile.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_login">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="10dp" />

    <TextView
        android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/image"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:text="Username"
        android:textColor="#ffffff"
        android:textSize="22sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/username"
        android:orientation="vertical"
        android:padding="36dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="Full Name"
            android:textColor="#ffffff" />

        <EditText
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:backgroundTint="#ffffff"
            android:inputType="text|textCapWords"
            android:maxLines="1"
            android:textColor="#ffffff"
            android:textColorHint="#ffffff" />

        <Button
            android:id="@+id/btn_update"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:text="Update"
            android:textSize="12sp" />
    </LinearLayout>
</RelativeLayout>

In android, dp – Density-independent Pixels – is an abstract unit that is based on the physical density of the screen. These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with the screen density, but not necessarily in direct proportion.

  1. We have a circular image view where user’s profile picture will be displayed. User can click on picture to edit.
  2. Then name of user below profile picture.
  3. A <LinearLayout> to display input fields and button vertically aligned.
  4. <EditText> to change name. You can add more fields, that depends on your project.
  5. And a <Button> which when clicked will submit the form and send an HTTP request to the PHP server to save the picture in folder and its path and in MySQL database. Also to store the name in MySQL.

Now in your profile activity’s onCreate method paste the following code. We will explain that line-by-line:

ProfileActivity.java

private final int PICK_IMAGE_REQUEST = 71;

private EditText name;
private ImageView image;
private Button btnUpdate;

private String selectedPicture = "";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_profile);

    name = findViewById(R.id.name);

    image = findViewById(R.id.image);
    image.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent();
            intent.setType("image/*");
            intent.setAction(Intent.ACTION_GET_CONTENT);
            startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE_REQUEST);
        }
    });
}

When image is clicked we will send an intent telling the android to open the gallery for images along with request ID in a variable PICK_IMAGE_REQUEST.

On image selected from gallery – Android

You will be asked to select an image from gallery. When the image is selected and the app is opened again, an event will be received in onActivityResult() function, so we will override it. It will contain the image data:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK
            && data != null && data.getData() != null) {
        Uri filePath = data.getData();
        try {
            Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath);
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            byte[] imageBytes = baos.toByteArray();
            selectedPicture = Base64.encodeToString(imageBytes, Base64.DEFAULT);

            byte[] bytesImage = Base64.decode(selectedPicture, Base64.DEFAULT);
            Glide.with(getApplicationContext())
                    .load(bytesImage)
                    .asBitmap()
                    .into(image);
        } catch (IOException e) {
            Toast.makeText(getApplicationContext(), "Error: " + e.getMessage(), Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
    }
}

We are converting user selected image into Bitmap and converting that into base64 since we can only upload the android gallery image on server via base64. Saving the base64 string in variable named selectedPicture. And then displaying that in image view using Glide.

You can compress the image if you want. And you can decide to set the format to PNG or JPEG.

Upload image to PHP server

Now when update button is clicked, we will send an HTTP request containing base64 string of picture, along with name, and ID of user. You might be getting the user ID from shared preference. Learn how to save value in shared preference from here. Paste the following code in your activity’s onCreate() method.

btnUpdate = findViewById(R.id.btn_update);
btnUpdate.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String url = "http://localhost/tutorials/Http.php";
        // Instantiate the RequestQueue.
        RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Toast.makeText(getApplicationContext(), "Profile has been updated", Toast.LENGTH_LONG).show();
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        }) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("id", "9");
                params.put("name", name.getText().toString());
                params.put("picture", selectedPicture);
                params.put("update_profile", "1");

                return params;
            }

            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> params = new HashMap<>();
                params.put("Content-Type", "application/x-www-form-urlencoded");
                return params;
            }
        };

        // To prevent timeout error
        stringRequest.setRetryPolicy(new DefaultRetryPolicy(50000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        // Add the request to the RequestQueue.
        stringRequest.setShouldCache(false);
        queue.add(stringRequest);
    }
});
  1. We are sending a request using Volley to Http.php, this will be the file which will save the image file and save its path in MySQL database.
  2. Request will be POST and it must have parameters of username, picture base64 picture and user ID. You can get user ID from shared preference. Learn how to save value in shared preference from here.
  3. When the response is received from server, then we are simply displaying a success message in Toast.
  4. We are setting retry policy to 50 seconds, which means that the request will wait for 50 seconds till the response is received from server.
  5. And we have disable the cache so it will always return fresh response from server.

Now we will move to the server side. In your Http.php file, paste the following code and we will explain that line-by-line:

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$conn = mysqli_connect("localhost", "root", "root", "tutorials");

if (isset($_POST["update_profile"]))
{
    $id = $_POST["id"];
    $name = $_POST["name"];
    $picture = $_POST["picture"];

    $file_path = "profile/" . $id . ".jpg";

    file_put_contents($file_path, base64_decode($picture));

    $sql = "UPDATE `users` SET `name`='" . $name . "',`picture`='" . $file_path . "' WHERE id='" . $id . "'";
    mysqli_query($conn, $sql);

    echo "Profile has been updated";
    exit();
}

?>
  1. First we are enabling all error reporting for PHP.
  2. Then we are connecting with database.
  3. Then check if the request is for update profile. We are sending this value from Volley from android.
  4. Then get the name and base64 string of picture.
  5. Create a folder named “profile” in your project’s root directory.
  6. Then decode the base64 string and file_put_contents will save the file in “profile” folder.
  7. Finally update the MySQL database and send the response back to android app.

Display image from live server

Now that you have successfully uploaded an image from your android app to PHP server and saved its path in MySQL database. Now is the time to display that when user open the profile activity. Paste the following code in the onCreate() method when you successfully get the user records from database:

Glide.with(this)
    .load("http://localhost/tutorials/profile/9.jpg")
    .asBitmap()
    .diskCacheStrategy(DiskCacheStrategy.NONE)
    .skipMemoryCache(true)
    .into(image);

Where in load() function you will place the user actual image file store in your PHP server.

By default, Glide using caching to load images faster which are already loaded. But during development, we always want to fetch fresh data. So you can disable caching by setting DiskCacheStrategy to none.

So, now you can successfully upload image from your android gallery and save it in PHP server.

If you want to know how to fetch data from PHP and MySQL in your android app, you can follow this.

User registration in Android, Java, PHP, and MySQL

User registration is used in almost every android app today.

We will create a registration form in an android application using Java. And when the form is submitted, we will send an HTTP request from android to the PHP server that will save the user’s data in the MySQL database.

MySQL table structure

Following will be the table structure for users table:

--
-- Table structure for table `users`
--

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` text NOT NULL,
  `username` text NOT NULL,
  `password` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for table `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

Users table will have a unique ID that will be used to uniquely identify the user in the whole users table. We are adding a “primary key” index to the ID field. And also an “AUTO_INCREMENT” that will automatically generate the next ID number. For example, if there are 4 users already registered and a new user tries to register, then the MySQL database will automatically assign his ID to 5.

User registration in android – Layout

Now come to the android part. First, we will be creating a layout for the registration form. To keep the things simple, we are using name, username, and password in the registration form:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_login"
    android:paddingLeft="36dp"
    android:paddingTop="15dp"
    android:paddingRight="36dp">

    <ImageView
        android:id="@+id/logo"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        android:src="@drawable/logo" />

    <TextView
        android:id="@+id/tv_Signup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/logo"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:text="Create an account"
        android:textColor="#ffffff"
        android:textSize="20sp" />

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_Signup"
        android:backgroundTint="@android:color/white"
        android:hint="Full Name"
        android:inputType="text|textCapWords"
        android:maxLines="1"
        android:textColor="#ffffff"
        android:textColorHint="#ffffff" />

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/name"
        android:backgroundTint="@android:color/white"
        android:digits="qwertyuiopasdfghjklzxcvbnm1234567890"
        android:hint="Username"
        android:inputType="text"
        android:maxLines="1"
        android:textColor="#ffffff"
        android:textColorHint="#ffffff" />

    <android.support.v7.widget.AppCompatEditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/username"
        android:backgroundTint="@android:color/white"
        android:drawablePadding="5dp"
        android:hint="Password"
        android:inputType="textPassword"
        android:maxLines="1"
        android:textColor="#ffffff"
        android:textColorHint="#ffffff"
        android:textCursorDrawable="@null" />

    <Button
        android:id="@+id/btn_register"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_below="@+id/password"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:text="Register" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btn_login"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp" />
</RelativeLayout>

In android, dp – Density-independent Pixels – is an abstract unit that is based on the physical density of the screen. These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with the screen density, but not necessarily in direct proportion.

  1. <RelativeLayout> it will have full width and height to the screen, has an image as background (as in the screenshot). Will have padding from left (36dp), right (36dp), and top (15dp).
  2. <ImageView> to display the logo of the app or company. It has 100dp widths and 100dp height
  3. <TextView> to display the heading of the page.
  4. <EditText> is the input field for name. It will be below the heading. Its text will be in white color. Each word will be capitalized.
  5. Second <EditText> is for username. It will be below the name field. You can only write a-z and 0-9 digits, no special characters will be allowed.
  6. <android.support.v7.widget.AppCompatEditText> this field is for password. User cannot see the text entered in this field.
  7. <Button> which when clicked will submit the form and send an HTTP request to PHP server to store that data in MySQL database.
  8. Finally a <ProgressBar> to display a loading circle when the request is sent to the server. And will be hidden when the response is received from the server.

Call HTTP request – Volley

In your app/build.gradle, make sure to add Volley dependency:

dependencies {
    ....

    implementation 'com.android.volley:volley:1.1.1'
}

Now in your register activity’s onCreate method paste the following code. We will explain that line-by-line:

ProgressBar progressBar = findViewById(R.id.progressBar);
progressBar.setVisibility(View.GONE);

EditText username = findViewById(R.id.username);
EditText password = findViewById(R.id.password);
EditText name = findViewById(R.id.name);

Button btn_register = findViewById(R.id.btn_register);
btn_register.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {

        String usernameText = username.getText().toString().trim();
        String passwordText = password.getText().toString().trim();
        String nameText = name.getText().toString().trim();

        if (!usernameText.isEmpty() && !passwordText.isEmpty() && !nameText.isEmpty()) {

            progressBar.setVisibility(View.VISIBLE);
            btn_register.setEnabled(false);

            String url = "http://localhost/tutorials/Http.php";
            // Instantiate the RequestQueue.
            RequestQueue queue = Volley.newRequestQueue(this);

            // Request a string response from the provided URL.
            StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
                            try {
                                JSONObject responseJson = new JSONObject(response);
                                String status = responseJson.getString("status");
                                String message = responseJson.getString("message");

                                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

                                progressBar.setVisibility(View.GONE);
                                btn_register.setEnabled(true);
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    error.printStackTrace();
                    Toast.makeText(getApplicationContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
                }
            }) {
                @Override
                protected Map<String, String> getParams() {
                    Map<String, String> params = new HashMap<>();
                    params.put("name", name.getText().toString().trim());
                    params.put("username", username.getText().toString().trim());
                    params.put("password", password.getText().toString().trim());
                    params.put("registration", "1");

                    return params;
                }

                @Override
                public Map<String, String> getHeaders() throws AuthFailureError {
                    Map<String, String> params = new HashMap<>();
                    params.put("Content-Type", "application/x-www-form-urlencoded");
                    return params;
                }
            };

            // To prevent timeout error
            stringRequest.setRetryPolicy(new DefaultRetryPolicy(50000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

            // Add the request to the RequestQueue.
            stringRequest.setShouldCache(false);
            queue.add(stringRequest);

        } else {
            Toast.makeText(getApplicationContext(), "Please fill all details", Toast.LENGTH_LONG).show();
        }
    }
});
  1. First, we are creating an instance of ProgressBar and hiding it by default. We will only display when the request is sent.
  2. Then we are getting the name, username, and password input fields objects.
  3. After that, we are checking if any of the fields are empty, then display an error in Toast.
  4. Now, we are displaying the progress bar and disabling register button, so the user cannot click multiple times.
  5. Then we are sending a request using Volley. The request method should be POST.
  6. getParams() will have all our parameters. We are sending a name, username, and password along with an additional value “registration” that will be used to tell the server to handle the request for registration.
  7. Response received from the server will be a JSON string, so we are converting that into JSONObject, and displaying server response message in a Toast.
  8. And finally, we are hiding the progress bar and enable the register button again. So if there is any error, then the user can click on the register button again.

Save user data in MySQL using PHP

Now come to the server-side. As you see we are sending the request to Http.php so we must have that file in our server. This file should have the following content, we will explain that line-by-line:

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$conn = mysqli_connect("localhost", "root", "root", "tutorials");

if (isset($_POST["registration"]))
{
    $username = $_POST["username"];
    $name = $_POST["name"];

    $sql = "SELECT * FROM `users` WHERE `username` = '" . $username . "'";
    $result = mysqli_query($conn, $sql);

    if (mysqli_num_rows($result) > 0)
    {
        echo json_encode(array(
            "status" => "error",
            "message" => "Username already exists"
        ));
        exit();
    }

    $password = password_hash($_POST["password"], PASSWORD_DEFAULT);

    $sql = "INSERT INTO `users`(`name`, `username`, `password`) VALUES ('" . $name . "', '" . $username . "', '" . $password . "')";
    mysqli_query($conn, $sql);

    echo json_encode(array(
        "status" => "success",
        "message" => "Account has been created",
        "user_id" => mysqli_insert_id($conn) . ""
    ));
    exit();
}
  1. First, we are enabling all PHP errors. So if there is any error it will be received in error response in the android app.
  2. Then we are connecting with the database using host: port, username, password, and database name respectively.
  3. After that, we are checking if the request received is for registration, then get the name, username, and password fields from a POST request.
  4. Now, check from the database if the user with the same username already exists in the user’s table, if yes, then send an error response and stop the script using exit() function.
  5. Then convert the plain text password into an encrypted hash, so even if your database got hacked, no one will know the actual password of your users.
  6. And finally, run an INSERT query to save the database in MySQL database and send the success response back to the android app.
  7. We are also sending the inserted ID in response. So if you want to use the newly inserted ID in the MySQL table, you can get it from mysqli_insert_id function and passing the connection as a parameter.

User registration has been done in your android project. You can also learn how to authenticate the user for login and save his data in android Shared Preferences.

class file for com.google.android.gms.internal.zzbfm not found Firebase and Google maps – Solution

Today, we will share with you the solution for class “zzbfm” not found error while working on Firebase and Google maps.

If you are ever facing an error in android that says:

class file for com.google.android.gms.internal.zzbfm not found

It is most likely when you just integrated Firebase library in your project that is already using maps, location or places.

The solution to this is:

  1. Version number of google maps and Firebase should be same.
  2. Change “implementation fileTree” to “compileOnly fileTree”
  3. Enable multiDex and add multiDex library

app > build.gradle

android {
    compileSdkVersion 28
//    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "your.application.id"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        multiDexEnabled true // important
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
//    implementation fileTree(include: ['*.jar'], dir: 'libs')
    compileOnly fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.google.android.gms:play-services-maps:11.8.0'
    implementation 'com.google.android.gms:play-services-location:11.8.0'
    implementation 'com.google.android.gms:play-services-places:11.8.0'

    implementation 'com.google.firebase:firebase-core:11.8.0'
    implementation 'com.google.firebase:firebase-messaging:11.8.0'
    implementation 'com.google.firebase:firebase-database:11.8.0'

    implementation 'com.android.support:multidex:1.0.3'
}
apply plugin: 'com.google.gms.google-services'

build.gradle

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath 'com.google.gms:google-services:4.0.1'
    }
}

If your problem persists, please mention it in the comments section below. Check Google cloud platform for more services.

Learn how to embed maps without API key.

Maps without API Key – By Coordinates & By Address

Android cache SQLite

You can optimize your app performance by using cache data in Android using SQLite. We will be using Stetho library to see the cached data.

Basic use of cache in SQLite is to display frequently accessed data faster in your android app. And to reduce the overhead on the server. There are already tons of libraries available that automatically handles the API cache data for you. But nothing gives more control than writing the code by yourself. So in this tutorial, we will create a custom cache mechanism in SQLite. So we can decide when to expire the cache and which data needs to be cached in android system and which should not.

Volley default cache

By default, Volley does provide a method to cache the API response data. But it only caches the response which is already been received using Volley request. Consider this, you have a list of employee records which are being fetched from API. And then you have another API which receives employee ID and gives you detail about that employee. If you are using Volley’s cache mechanism then it only displays that employee data faster whose details you have already been seen. But using your custom technique, you can save all employees data in the SQLite cache. And get the detail of any employee using their ID.

1. Create an API

You might have your own API code based on your project, but for sake of this tutorial we have create a basic API which connects with database. Fetch all rows from employees table, push the data in an array and send the response back in JSON. We could have use the mysqli_fetch_all() function but it is not supported in some of the PHP versions. So following this technique you will be able to use this without having to worry about version of PHP.

<?php

$conn = mysqli_connect("localhost", "root", "", "classicmodels");
$result = mysqli_query($conn, "SELECT * FROM employees");

$data = array();
while ($row = mysqli_fetch_object($result))
{
    array_push($data, $row);
}
echo json_encode($data);

?>

Setup android project

We have created a new android project to demonstrate the working of SQLite cache. But you might be implementing this in your own project. So we will only discuss the libraries you need to install. And some minor settings you need to do in some of your project files. Open your app > build.gradle file and paste the following dependencies in it:

implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.volley:volley:1.1.1'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.facebook.stetho:stetho:1.5.1'

You can change the version number as per your project’s target SDK version. Android studio will highlight the dependencies if there is an update in Volley, Gson and Stetho libraries. After adding these dependencies, you need to sync the project. Then add internet permission in your AndroidManifest.xml file before starting of application tag. This will help you to call HTTP requests:

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

Stetho library will be used to display SQLite inside google chrome inspect tools. Create a new class named MyApplication.java and extend it with Application:

package com.adnan.app.sqlitecache;

import android.app.Application;
import com.facebook.stetho.Stetho;

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Stetho.initializeWithDefaults(this);
    }
}

This will initialize the Stetho library and whenever you have some data in SQLite database tables, you will be able to see it by running chrome://inspect in your browser address bar (where you put website URL). Then set this class as application main class by giving name attribute to application tag in AndroidManifest.xml:

<application
        android:name=".MyApplication"

Setup layouts

Now we will setup the layout to create a recycler view where all employees data will be displayed. Also an adapter layout where each single employee data will be displayed. Create an adapter class which will hold each employee item, and a model class which tells the structure of data received from server. Create a recycler view in your activity layout file and get its instance in activity class file and initialize it.

EmployeeModel.java

package com.adnan.app.sqlitecache.models;

public class EmployeeModel {

    private String firstName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

EmployeeAdapter.java

package com.adnan.app.sqlitecache.adapters;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.adnan.app.sqlitecache.R;
import com.adnan.app.sqlitecache.models.EmployeeModel;

import java.util.ArrayList;

public class EmployeeAdapter extends RecyclerView.Adapter {

    private Context context;
    private ArrayList models;

    public EmployeeAdapter(Context context, ArrayList models) {
        this.context = context;
        this.models = models;
    }

    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(context).inflate(R.layout.single_employee, viewGroup, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
        viewHolder.name.setText(models.get(i).getFirstName());
    }

    @Override
    public int getItemCount() {
        return models.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private TextView name;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            name = itemView.findViewById(R.id.name);
        }
    }
}

We are passing the activity context and models array from activity to adapter using adapter’s constructor. Context will help to setup the single layout file and models array helps to tell how many items should be rendered in this adapter and each item’s index.

single_employee.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_margin="20dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Name"
        android:id="@+id/name"/>

</RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rv" />

</RelativeLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
	private RecyclerView rv;
	private EmployeeAdapter adapter;
	private ArrayList models;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		rv = findViewById(R.id.rv);
		rv.setHasFixedSize(true);

		LinearLayoutManager layoutManager = new LinearLayoutManager(this);
		rv.setLayoutManager(layoutManager);

		models = new ArrayList<>();
		adapter = new EmployeeAdapter(this, models);
		rv.setAdapter(adapter);
	}
}

Get data from API

We will be using Volley library to send an HTTP request to API and fetch response as JSON string. Make sure you have added Volley dependency and synced the project. Also, make sure you have added internet permission in your android manifest. Create a new method in activity class and call it from onCreate method after the adapter has been set in recycler view.

private void getData() {
	String url = "Your API URL";
	RequestQueue requestQueue = Volley.newRequestQueue(this);

	StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
		@Override
		public void onResponse(String response) {
			Log.i("my_log", response);
		}
	}, new Response.ErrorListener() {
		@Override
		public void onErrorResponse(VolleyError error) {
			Log.i("my_log", error.getMessage());
		}
	});

	requestQueue.add(stringRequest);
}

Here our request method is GET and you can place your API URL in a variable named url. At this point, if you run the app in debug mode and open your logcat, you will be able to see the response after a few seconds (depends on the server and query execution time). If you saw any error in logcat, make sure you have added internet permission and double-check your API URL set in url variable.

This function will be called when the app is ran for the first time. Because for the first time, the android cache will empty since there is no data in SQLite database.

Mirror android to PC or Mac

Sometimes you might want to see your android device in your PC or Mac. You can use Vysor app which is available for both Mac and Windows, however, we do not recommend that because of too many pop-up ads. But we recommend to use the scrcpy library. You can install it from instructions on GitHub site and to run it, simply attach your android device and run the following command in your terminal:

> scrcpy

Convert JSON to ArrayList

We will be using Gson library to convert JSON string into Java array list. In onResponse of Volley request, create a try catch block because converting JSON to array will throw an exception if the data is not in correct JSON format. Even if you have any special character in your database, it will not be able to parse JSON data. We need to use the TypeToken class to convert json to array, if it were simple object then we would have simply used EmployeeModel.class but in case of array, we have to use TypeToken.

try {
	Gson gson = new Gson();
	Type type = new TypeToken<ArrayList<EmployeeModel>>() {}.getType();
	models = gson.fromJson(response, type);
} catch (Exception e) {
	Log.i("my_log", e.getMessage());
}

Filter data in adapter

Create a new method named showData() in activity and call it after the JSON has been converted into array list of models.

MainActivity.java

private void showData() {
	adapter.setFilter(models);
}

And in adapter add a new method which will remove old model data and add new data, and update the adapter to render the items again. The reason we are doing this is because at the time of assigning adapter to recycler view, our arraylist was empty. So if we do not use this method, then we will not be able to see any data in recycler view.

EmployeeAdapter.java

public void setFilter(ArrayList data) {
	this.models.clear();
	this.models.addAll(data);
	notifyDataSetChanged();
}

Show progress bar

If you want to display progress bar inside each adapter item, you can place this progress bar tag in your adapter layout file. This is typically useful when you are displaying images from HTTP, as they may take some time to display, so you can show a progress bar when the image is fully loaded. But here we will be displaying one progress bar in center of the screen, so we are creating progress bar tag in activity layout.

activity_main.xml

<ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:id="@+id/progressBar"/>

MainActivity.java

private ProgressBar progressBar;

// in onCreate after setContentView
progressBar = findViewById(R.id.progressBar);

// update showData method
private void showData() {
	adapter.setFilter(models);
	progressBar.setVisibility(View.GONE);
}

Setup SQLite

Create a new class named SQLiteManager and extend it from SQLiteOpenHelper. It has 2 abstract methods, onCreate and onUpgrade so you must implement them in your class. onCreate will be called only once, but onUpgrade will be called whenever you change your database version. Create a constructor for this class and call the super constructor, in super constructor we will tell the database name and version. In onCreate method, you will set your all tables structure (DDL). You can perform any function you want in onUpgrade method, but the common practice is, since we are upgrading the database version, it means that we may have remove some columns and have added some new columns, may be created a new table altogether. So the best practice is to remove all old tables structures and all data in them. Then call the onCreate method manually.

SQLiteManager.java

package com.adnan.app.sqlitecache.managers;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import com.adnan.app.sqlitecache.models.EmployeeModel;
import java.util.ArrayList;

public class SQLiteManager extends SQLiteOpenHelper {

    public SQLiteManager(Context context) {
        super(context, "android_cache", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS employees(" +
                "name TEXT NOT NULL)";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE employees");
        onCreate(db);
    }
}

Insert data in SQLite

Add the following method in your SQLiteManager class:

SQLiteManager.java

public void addData(EmployeeModel employeeModel) {
	SQLiteDatabase sqLiteDatabase = this.getWritableDatabase();
	ContentValues contentValues = new ContentValues();

	contentValues.put("name", employeeModel.getFirstName());
	sqLiteDatabase.insert("employees", null, contentValues);
	sqLiteDatabase.close();
}

Now in your activity class, in Volley onResponse method, when the JSON response has been converted to array list models, we will loop through all data in array and call this function for each employee. So create an instance of SQLiteManager class and initialize it and do the following:

MainActivity.java

private SQLiteManager sqLiteManager;

// in onCreate
sqLiteManager = new SQLiteManager(this);

// in Volley onResponse after models = gson.fromJson(response, type);
for (int a = 0; a < models.size(); a++) {
	sqLiteManager.addData(models.get(a));
}

This will add the data in SQLite database whenever we call an API request. But this will always append new data, so we need to find a way to delete old data whenever new data is received from API.

Delete data from SQLite

Create the following function in your SQLiteManager class:

SQLiteManager.java

public void deleteOldCache() {
	SQLiteDatabase sqLiteDatabase = this.getWritableDatabase();
	sqLiteDatabase.execSQL("DELETE FROM employees");
	sqLiteDatabase.close();
}

And call this function from your activity class:

MainActivity.java

// in Volley onResponse before for (int a = 0; a < models.size(); a++) and after models = gson.fromJson(response, type);
sqLiteManager.deleteOldCache();

View data from SQLite

At this point, the data has been saved correctly in SQLite. Now we need to make the app to read from SQLite database if there is any data, otherwise the data will be fetched from API. So the first time when app gets installed, it will not have any data in SQLite, so it will fetch from API and save in SQLite. Next time it will found the data, so it will read it from SQLite instead of sending the API request again.

SQLiteManager.java

public ArrayList getData() {
	ArrayList data = new ArrayList<>();
	SQLiteDatabase sqLiteDatabase = this.getWritableDatabase();
	Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM employees", null);

	if (cursor.moveToFirst()) {
		do {
			EmployeeModel employeeModel = new EmployeeModel();
			employeeModel.setFirstName(cursor.getString(0));
			data.add(employeeModel);
		} while (cursor.moveToNext());
	}

	return data;
}

MainActivity.java

ArrayList cache = sqLiteManager.getData();

if (cache.size() > 0) {
	models = cache;
	showData();
} else {
	getData();
}

Now it will only fetch the data from API once and saved it in SQLite. Next time it will fetch from SQLite rather than from API. But the only problem is, it will always fetch it from SQLite. So we need to find a way to expire the cache after some time.

Set cache expiry time

You can set the expiry time of cache in simple seconds and you can do the math of converting seconds into minutes and hours and days etc. For example, if you want to expire the cache after 18 hours, you can simply do (60 * 60 * 18 = 64800). We will be using android shared preferences to store the time when data was cached or saved in SQLite. Then before checking if to get data from cache or from API, we need to check if the cache has been expired. We can do that by taking the difference between current time and the time when data was cached. Since Java date function returns time in milliseconds, so we can simply convert them to seconds by dividing them with 1000. Then our condition will say:

Check if there is any data in cache AND the cache is not expired.

MainActivity.java

private SharedPreferences preferences;

// in onCreate
preferences = PreferenceManager.getDefaultSharedPreferences(this);

boolean isCacheExpire = false;
long cacheTime = preferences.getLong("cache", 0);

if (cacheTime > 0) {
	long currentTime = new Date().getTime();
	long difference = currentTime - cacheTime;
	long seconds = difference / 1000;

	if (seconds > 20) {
		isCacheExpire = true;
	}
}

if (cache.size() > 0 && !isCacheExpire) {
	models = cache;
	showData();
} else {
	getData();
}

And set the cache time in shared preference in Volley onResponse method, after data has been saved in SQLite:

preferences.edit().putLong("cache", new Date().getTime()).apply();

Complete MainActivity.java

package com.adnan.app.sqlitecache;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;

import com.adnan.app.sqlitecache.adapters.EmployeeAdapter;
import com.adnan.app.sqlitecache.managers.SQLiteManager;
import com.adnan.app.sqlitecache.models.EmployeeModel;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;

public class MainActivity extends AppCompatActivity {
    private RecyclerView rv;
    private EmployeeAdapter adapter;
    private ArrayList models;
    private ProgressBar progressBar;
    private SQLiteManager sqLiteManager;
    private SharedPreferences preferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        progressBar = findViewById(R.id.progressBar);
        sqLiteManager = new SQLiteManager(this);
        preferences = PreferenceManager.getDefaultSharedPreferences(this);

        rv = findViewById(R.id.rv);
        rv.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        rv.setLayoutManager(layoutManager);

        models = new ArrayList<>();
        adapter = new EmployeeAdapter(this, models);
        rv.setAdapter(adapter);

        ArrayList cache = sqLiteManager.getData();
        boolean isCacheExpire = false;
        long cacheTime = preferences.getLong("cache", 0);

        if (cacheTime > 0) {
            long currentTime = new Date().getTime();
            long difference = currentTime - cacheTime;
            long seconds = difference / 1000;

            if (seconds > 20) {
                isCacheExpire = true;
            }
        }

        if (cache.size() > 0 && !isCacheExpire) {
            models = cache;
            showData();
        } else {
            getData();
        }
    }

    private void getData() {
        String url = "Your API URL here";
        RequestQueue requestQueue = Volley.newRequestQueue(this);

        StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() {
            @Override
            public void onResponse(String response) {
                try {
                    Gson gson = new Gson();
                    Type type = new TypeToken<ArrayList<EmployeeModel>>() {

                    }.getType();
                    models = gson.fromJson(response, type);
                    sqLiteManager.deleteOldCache();

                    for (int a = 0; a < models.size(); a++) {
                        sqLiteManager.addData(models.get(a));
                    }

                    preferences.edit().putLong("cache", new Date().getTime()).apply();

                    showData();
                } catch (Exception e) {
                    Log.i("my_log", e.getMessage());
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.i("my_log", error.getMessage());
            }
        });

        stringRequest.setShouldCache(false);
        requestQueue.add(stringRequest);
    }

    private void showData() {
        adapter.setFilter(models);
        progressBar.setVisibility(View.GONE);
    }
}

Run the app now. And you are all set in implementing SQLite cache in your android app. We have used android SQLite to save the data in the cache. But you can also use android’s shared preferences if the data that needs to be cached is relatively small.

[wpdm_package id=’211′]