Independent Developer Education Blog

Learn Android, React Native, JavaScript and real-world app development.

CodeChain Dev publishes practical tutorials, debugging guides, interview preparation, source-code explanations and mobile development notes for developers.

Topics Covered

  • Android, Kotlin and Java
  • React Native app development
  • JavaScript, APIs and debugging
  • SQL, Gradle and developer tools
Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Tuesday, October 6, 2020

How to Implement Color Picker Dialog in Android Using AmbilWarna Library

Color picker components are useful in many Android applications such as drawing apps, theme customization apps, note applications, and profile customization screens.

In this tutorial, we will learn how to implement a simple color picker in Android using the AmbilWarna library.

We will:

  • Add the AmbilWarna dependency
  • Open a color picker dialog
  • Select colors dynamically
  • Change the background color of the layout

What Is AmbilWarna?

AmbilWarna is a lightweight Android color picker library that provides a simple and customizable color selection dialog.

It is easy to integrate and works well for basic color selection use cases.


Step 1 — Add Dependency

Open your build.gradle file and add the following dependency:


implementation 'yuku.ambilwarna:ambilwarna:2.0.1'

After adding the dependency, sync your Gradle project.


Step 2 — Create Layout File

Create the UI inside activity_main.xml.


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

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Color Picker"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3 — Implement Color Picker Logic

Now open MainActivity.java and add the following code:


package com.example.colorpicker;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;

import yuku.ambilwarna.AmbilWarnaDialog;

public class MainActivity extends AppCompatActivity {

    ConstraintLayout layout;
    Button button;

    int defaultColor;

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

        layout = findViewById(R.id.layout);
        button = findViewById(R.id.button);

        defaultColor = ContextCompat.getColor(
                MainActivity.this,
                R.color.purple_500
        );

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                openColorPicker();
            }
        });
    }

    private void openColorPicker() {

        AmbilWarnaDialog colorPicker =
                new AmbilWarnaDialog(
                        this,
                        defaultColor,
                        new AmbilWarnaDialog.OnAmbilWarnaListener() {

                            @Override
                            public void onCancel(AmbilWarnaDialog dialog) {
                            }

                            @Override
                            public void onOk(
                                    AmbilWarnaDialog dialog,
                                    int color
                            ) {

                                defaultColor = color;

                                layout.setBackgroundColor(defaultColor);
                            }
                        });

        colorPicker.show();
    }
}

How This Implementation Works

The application works in the following steps:

  1. User clicks the button
  2. AmbilWarna dialog opens
  3. User selects a color
  4. Selected color is returned
  5. Background color updates dynamically

Expected Output

After running the application:

  • A button appears in the center of the screen
  • Clicking the button opens the color picker dialog
  • Selecting a color changes the activity background color

Common Mistakes Developers Make

1. Missing Dependency

If the library is not added correctly, Gradle sync will fail.


2. Wrong View ID

Ensure the IDs inside XML match the IDs used in Java code.


3. Theme Compatibility Issues

Some older Android themes may create UI inconsistencies with dialog appearance.


Modern Android Improvement Suggestions

For production-grade Android applications, developers can improve this implementation by:

  • Using Kotlin instead of Java
  • Using ViewBinding
  • Saving selected color using SharedPreferences
  • Supporting dark mode
  • Adding color preview components

Example: Saving Selected Color


SharedPreferences.Editor editor =
        getSharedPreferences("settings", MODE_PRIVATE).edit();

editor.putInt("selected_color", defaultColor);
editor.apply();

This allows the application to restore the selected color after reopening the app.


FAQ

Can this library work with Kotlin?

Yes. AmbilWarna works with both Java and Kotlin Android projects.

Can I customize the color picker dialog?

Yes. Developers can modify dialog appearance or use alternative libraries for advanced customization.

Is AmbilWarna suitable for production apps?

Yes, for basic color selection functionality. However, large applications may require more advanced UI customization.


Conclusion

Implementing a color picker in Android is simple using the AmbilWarna library.

This approach is useful for applications that require dynamic UI customization or drawing-related features.

Modern Android applications can further improve this implementation using Kotlin, Material Design components, and persistent theme settings.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS platforms, fintech applications, and developer tooling systems.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Sunday, October 4, 2020

How to Request Runtime Permissions in Android Using EasyPermissions Library

Runtime permissions became mandatory in Android starting from Android Marshmallow (API level 23).

Instead of automatically granting permissions during installation, Android now asks users to approve dangerous permissions while the app is running.

Managing permissions manually can become repetitive and difficult, especially when handling multiple permissions and permanently denied states.

In this tutorial, we will learn how to use the EasyPermissions library in Android to request runtime permissions more efficiently.


What Is EasyPermissions?

EasyPermissions is a lightweight Android library developed to simplify runtime permission handling.

It helps developers:

  • Request multiple permissions
  • Handle permission callbacks
  • Show rationale dialogs
  • Handle permanently denied permissions
  • Redirect users to app settings

Why Runtime Permissions Are Important

Dangerous permissions allow access to sensitive device features such as:

  • Camera
  • Storage
  • Location
  • Microphone
  • Contacts

Android requires developers to request these permissions during runtime for better user privacy and security.


Step 1 — Add Dependency

Open your build.gradle file and add the following dependency:


implementation 'pub.devrel:easypermissions:3.0.0'

After adding the dependency, sync your Gradle project.


Step 2 — Add Permissions in AndroidManifest.xml

Open AndroidManifest.xml and add the required permissions.


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

These permissions are required for camera access and reading device storage.


Step 3 — Create Layout File

Create the UI inside activity_main.xml.


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

<androidx.constraintlayout.widget.ConstraintLayout
    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">

    <Button
        android:id="@+id/button_open_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Camera"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4 — Implement Permission Handling

Open MainActivity.java and add the following code:


package com.example.easypermissionexample;

import android.Manifest;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import java.util.List;

import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.AppSettingsDialog;
import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends AppCompatActivity
        implements EasyPermissions.PermissionCallbacks {

    private static final int REQUEST_CODE = 123;

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

        Button buttonOpenCamera =
                findViewById(R.id.button_open_camera);

        buttonOpenCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                openCamera();
            }
        });
    }

    @AfterPermissionGranted(REQUEST_CODE)
    private void openCamera() {

        String[] perms = {
                Manifest.permission.CAMERA,
                Manifest.permission.READ_EXTERNAL_STORAGE
        };

        if (EasyPermissions.hasPermissions(this, perms)) {

            Toast.makeText(
                    this,
                    "Permissions Granted",
                    Toast.LENGTH_SHORT
            ).show();

        } else {

            EasyPermissions.requestPermissions(
                    this,
                    "Camera and Storage permissions are required",
                    REQUEST_CODE,
                    perms
            );
        }
    }

    @Override
    public void onPermissionsGranted(
            int requestCode,
            @NonNull List<String> perms
    ) {
    }

    @Override
    public void onPermissionsDenied(
            int requestCode,
            @NonNull List<String> perms
    ) {

        if (EasyPermissions.somePermissionPermanentlyDenied(
                this,
                perms
        )) {

            new AppSettingsDialog.Builder(this)
                    .build()
                    .show();
        }
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults
    ) {

        super.onRequestPermissionsResult(
                requestCode,
                permissions,
                grantResults
        );

        EasyPermissions.onRequestPermissionsResult(
                requestCode,
                permissions,
                grantResults,
                this
        );
    }
}

How This Implementation Works

The permission workflow is:

  1. User clicks the button
  2. Application checks permissions
  3. If permissions are granted, camera logic runs
  4. If permissions are denied, Android permission dialog appears
  5. If user permanently denies permissions, app settings dialog opens

Handling Permanently Denied Permissions

When users check:

Don't ask again

Android will stop showing the permission dialog.

EasyPermissions helps redirect users to:

  • App Settings
  • Permission Management Screen

This improves user experience significantly.


Common Mistakes Developers Make

1. Forgetting Manifest Permissions

Runtime requests will fail if permissions are missing inside the manifest file.


2. Not Handling Denied Permissions

Applications should gracefully handle denied states instead of crashing.


3. Requesting Too Many Permissions

Request only the permissions required for the feature currently being used.


Modern Android Permission Recommendations

For production Android applications:

  • Request permissions only when needed
  • Explain clearly why permissions are required
  • Handle denied states properly
  • Support Android 13+ permission changes
  • Use Activity Result APIs when possible

EasyPermissions vs Manual Permission Handling

EasyPermissions Manual Handling
Cleaner code More boilerplate
Built-in rationale support Manual dialog management
Settings redirection support Manual intent handling

FAQ

Is EasyPermissions still useful in 2026?

Yes. It remains useful for simplifying Android runtime permission handling.

Can I request multiple permissions together?

Yes. EasyPermissions supports requesting multiple permissions in a single dialog.

What happens if the user permanently denies permission?

The application should redirect users to App Settings so they can manually grant permissions.


Conclusion

Runtime permission handling is an important part of modern Android development.

The EasyPermissions library simplifies permission requests, improves readability, and helps developers manage permission states efficiently.

Using proper permission handling improves application security, user trust, and overall user experience.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS products, fintech applications, and developer tooling platforms.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Thursday, October 1, 2020

How to Validate Email and Password in Android Using Regex

Form validation is an important part of modern Android application development.

Applications commonly validate:

  • Email addresses
  • Usernames
  • Passwords
  • Phone numbers

Proper client-side validation improves user experience, reduces invalid API requests, and helps maintain better data quality.

In this tutorial, we will learn how to validate email addresses and passwords in Android using Regular Expressions (Regex).


What Is Regex?

Regex (Regular Expression) is a pattern matching technique used to validate and process text data.

Developers use regex for:

  • Email validation
  • Password rules
  • Phone number formatting
  • Input filtering

What We Will Build

In this Android example, we will:

  • Validate email format
  • Validate username length
  • Validate strong passwords
  • Display error messages using Material TextInputLayout

Step 1 — Add Material Design Dependency

Open your build.gradle file and add:


implementation 'com.google.android.material:material:1.11.0'

Then sync the Gradle project.


Step 2 — Create Layout File

Create the UI inside activity_main.xml.


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

<LinearLayout
    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:orientation="vertical"
    android:padding="16dp">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/text_input_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Email"
            android:inputType="textEmailAddress"/>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/text_input_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:counterEnabled="true"
        app:counterMaxLength="15">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Username"/>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/text_input_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:endIconMode="password_toggle">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Password"
            android:inputType="textPassword"/>

    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/button_confirm"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Confirm"/>

</LinearLayout>

Step 3 — Implement Validation Logic

Open MainActivity.java and add the following code:


package com.example.validationapp;

import android.os.Bundle;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.textfield.TextInputLayout;

import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity {

    private static final Pattern PASSWORD_PATTERN =
            Pattern.compile("^" +
                    "(?=.*[a-zA-Z])" +
                    "(?=.*[@#$%^&+=])" +
                    "(?=\\\\S+$)" +
                    ".{6,}" +
                    "$");

    private TextInputLayout textInputEmail;
    private TextInputLayout textInputUsername;
    private TextInputLayout textInputPassword;

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

        textInputEmail =
                findViewById(R.id.text_input_email);

        textInputUsername =
                findViewById(R.id.text_input_username);

        textInputPassword =
                findViewById(R.id.text_input_password);

        Button buttonConfirm =
                findViewById(R.id.button_confirm);

        buttonConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                confirmInput();
            }
        });
    }

    private boolean validateEmail() {

        String emailInput =
                textInputEmail.getEditText()
                        .getText()
                        .toString()
                        .trim();

        if (emailInput.isEmpty()) {

            textInputEmail.setError(
                    "Field can't be empty"
            );

            return false;

        } else if (!Patterns.EMAIL_ADDRESS
                .matcher(emailInput)
                .matches()) {

            textInputEmail.setError(
                    "Please enter a valid email"
            );

            return false;

        } else {

            textInputEmail.setError(null);

            return true;
        }
    }

    private boolean validateUsername() {

        String usernameInput =
                textInputUsername.getEditText()
                        .getText()
                        .toString()
                        .trim();

        if (usernameInput.isEmpty()) {

            textInputUsername.setError(
                    "Field can't be empty"
            );

            return false;

        } else if (usernameInput.length() > 15) {

            textInputUsername.setError(
                    "Username too long"
            );

            return false;

        } else {

            textInputUsername.setError(null);

            return true;
        }
    }

    private boolean validatePassword() {

        String passwordInput =
                textInputPassword.getEditText()
                        .getText()
                        .toString()
                        .trim();

        if (passwordInput.isEmpty()) {

            textInputPassword.setError(
                    "Field can't be empty"
            );

            return false;

        } else if (!PASSWORD_PATTERN
                .matcher(passwordInput)
                .matches()) {

            textInputPassword.setError(
                    "Password too weak"
            );

            return false;

        } else {

            textInputPassword.setError(null);

            return true;
        }
    }

    private void confirmInput() {

        if (!validateEmail()
                | !validateUsername()
                | !validatePassword()) {

            return;
        }

        Toast.makeText(
                this,
                "Validation Successful",
                Toast.LENGTH_SHORT
        ).show();
    }
}

How Password Regex Works

The regex pattern checks:

  • At least one alphabet character
  • At least one special character
  • No whitespace
  • Minimum password length

Regex pattern used:


(?=.*[a-zA-Z])
(?=.*[@#$%^&+=])
(?=\\S+$)
.{6,}

How Email Validation Works

Android provides a built-in email validation pattern:


Patterns.EMAIL_ADDRESS.matcher(email).matches()

This helps validate whether the entered email follows standard email formatting.


Expected Output

After running the application:

  • Users can enter email, username, and password
  • Invalid fields display error messages
  • Valid inputs show success message

Common Validation Mistakes

1. Weak Password Rules

Weak passwords reduce application security.


2. Overly Strict Validation

Very strict regex rules may block legitimate user inputs.


3. Missing Server-Side Validation

Client-side validation improves UX, but backend validation is still required for security.


Modern Android Validation Improvements

Production Android applications should also consider:

  • Real-time validation
  • Kotlin implementation
  • ViewBinding
  • MVVM architecture
  • Server-side verification

FAQ

Why use regex for validation?

Regex helps developers validate text patterns efficiently.

Can regex validate all email addresses perfectly?

No. Regex can validate formatting, but complete email verification requires backend confirmation.

Should passwords be validated only on frontend?

No. Password validation should always happen on both frontend and backend.


Conclusion

Input validation is an important part of Android application development.

Using regex with Material TextInputLayout improves form validation, user experience, and application reliability.

Modern Android applications should combine client-side validation with secure backend verification for better security.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS platforms, fintech applications, and developer tooling systems.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Monday, September 28, 2020

How to Integrate UPI Payment Gateway in Android Using EasyUpiPayment Library

UPI (Unified Payments Interface) is one of the most widely used digital payment systems in India.

Many Android applications now support UPI payments for:

  • E-commerce payments
  • Subscription services
  • Donation systems
  • Recharge applications
  • Local business payments

In this tutorial, we will learn how to integrate UPI payments in Android using the EasyUpiPayment library.


What Is EasyUpiPayment?

EasyUpiPayment is an Android library that simplifies UPI payment integration.

It helps developers:

  • Launch UPI payment apps
  • Handle payment responses
  • Track transaction status
  • Reduce boilerplate code

Features of UPI Integration

  • Works with multiple UPI apps
  • Supports Google Pay, PhonePe, Paytm, BHIM
  • Easy transaction handling
  • Simple integration process

Important Requirements

  • Minimum SDK should be 19 or higher
  • Real device recommended for testing
  • At least one UPI app must be installed

Step 1 — Add Dependency

Open your build.gradle file and add:


implementation 'com.shreyaspatil:EasyUpiPayment:3.0.0'

Then sync your Gradle project.


Step 2 — Create Layout File

Create the UI inside activity_main.xml.


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

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <Button
        android:id="@+id/id_pay_using_upi_app"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pay Using UPI App"
        android:layout_gravity="center"/>

</FrameLayout>

Step 3 — Implement UPI Payment Logic

Open MainActivity.java and add the following code:


package com.example.upiintegration;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.shreyaspatil.easyupipayment.EasyUpiPayment;
import com.shreyaspatil.easyupipayment.listener.PaymentStatusListener;
import com.shreyaspatil.easyupipayment.model.PaymentApp;
import com.shreyaspatil.easyupipayment.model.TransactionDetails;

public class MainActivity extends AppCompatActivity
        implements PaymentStatusListener {

    private EasyUpiPayment easyUpiPayment;

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

        findViewById(R.id.id_pay_using_upi_app)
                .setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {
                        startUpiPayment();
                    }
                });
    }

    private void startUpiPayment() {

        String transactionId =
                "TID" + System.currentTimeMillis();

        String transactionRefId =
                "TREF" + System.currentTimeMillis();

        EasyUpiPayment.Builder builder =
                new EasyUpiPayment.Builder(this)
                        .with(PaymentApp.ALL)
                        .setPayeeVpa("example@upi")
                        .setPayeeName("Salil Jha")
                        .setTransactionId(transactionId)
                        .setTransactionRefId(transactionRefId)
                        .setDescription("Demo Payment")
                        .setAmount("100.00");

        try {

            easyUpiPayment = builder.build();

            easyUpiPayment.setPaymentStatusListener(this);

            easyUpiPayment.startPayment();

        } catch (Exception e) {

            e.printStackTrace();

            Toast.makeText(
                    this,
                    e.getMessage(),
                    Toast.LENGTH_SHORT
            ).show();
        }
    }

    @Override
    public void onTransactionCompleted(
            TransactionDetails transactionDetails
    ) {

        Toast.makeText(
                this,
                "Transaction Successful",
                Toast.LENGTH_SHORT
        ).show();
    }

    @Override
    public void onTransactionCancelled() {

        Toast.makeText(
                this,
                "Transaction Cancelled",
                Toast.LENGTH_SHORT
        ).show();
    }
}

How UPI Payment Flow Works

The payment flow is:

  1. User clicks payment button
  2. UPI payment app opens
  3. User completes payment
  4. UPI app returns transaction response
  5. Application receives payment status

Supported UPI Applications

The EasyUpiPayment library supports:

  • Google Pay
  • PhonePe
  • Paytm
  • BHIM
  • Other installed UPI apps

Important UPI Parameters

Parameter Description
Payee VPA Receiver UPI ID
Payee Name Name of receiver
Transaction ID Unique transaction identifier
Amount Payment amount

Common Mistakes Developers Make

1. Invalid UPI ID

Incorrect VPA format may cause payment failures.


2. Testing on Emulator

UPI apps usually do not work correctly on Android emulators.


3. Missing Transaction Verification

Frontend success messages alone are not secure for production applications.


Production-Level Security Recommendations

For real production systems:

  • Verify transactions on backend
  • Store transaction logs securely
  • Validate transaction IDs
  • Use payment webhooks
  • Prevent duplicate transactions

Backend Verification Importance

Frontend payment success should never be treated as final confirmation.

Production applications should always verify:

  • Transaction status
  • Payment amount
  • Transaction reference
  • Receiver account

This prevents fraud and fake payment confirmations.


Modern UPI Integration Improvements

Modern Android applications can improve UPI integration using:

  • Kotlin
  • MVVM architecture
  • Jetpack Compose
  • Backend transaction verification
  • Payment analytics

FAQ

Does UPI integration require a payment gateway?

Basic intent-based UPI integration does not require a gateway, but production systems should use proper backend verification.

Can I test UPI payments on emulator?

UPI testing works best on physical Android devices with installed UPI apps.

Is EasyUpiPayment suitable for production apps?

Yes, for basic UPI integration. However, production applications should implement backend transaction validation.


Conclusion

UPI integration allows Android applications to support fast and simple digital payments.

The EasyUpiPayment library simplifies payment integration and reduces development complexity.

For production-grade applications, developers should combine frontend payment flow with secure backend verification systems.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, fintech applications, blockchain systems, scalable SaaS platforms, and payment integrations.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Sunday, September 27, 2020

How to Save Activity State in Android Using onSaveInstanceState

Android applications can restart automatically during runtime configuration changes such as:

  • Screen rotation
  • Language changes
  • Dark mode changes
  • Multi-window mode

When this happens, the Activity is destroyed and recreated.

If developers do not save the UI state properly, variables and temporary data may be lost.

In this tutorial, we will learn how to save and restore Activity state in Android using onSaveInstanceState().


What Is onSaveInstanceState?

onSaveInstanceState() is an Android lifecycle callback used to save temporary UI data before an Activity gets destroyed.

Android automatically restores some UI elements such as:

  • EditText text
  • RecyclerView scroll position
  • Fragment states

However, custom variables must be saved manually.


Why Saving State Is Important

Without proper state handling:

  • Counters reset
  • User progress is lost
  • UI becomes inconsistent
  • User experience suffers

Saving Activity state improves application reliability and usability.


What We Will Build

In this example:

  • User can increment and decrement a counter
  • Counter value survives screen rotation
  • State restores automatically after Activity recreation

Step 1 — Create Layout File

Create the UI inside activity_main.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:gravity="center"
    android:orientation="horizontal">

    <Button
        android:id="@+id/button_decrement"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-" />

    <TextView
        android:id="@+id/text_view_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="0"
        android:textColor="@android:color/black"
        android:textSize="50sp" />

    <Button
        android:id="@+id/button_increment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+" />

</LinearLayout>

Step 2 — Implement MainActivity.java

Open MainActivity.java and add the following code:


package com.example.savedstateexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textViewCount;

    private int count;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        textViewCount =
                findViewById(R.id.text_view_count);

        Button buttonDecrement =
                findViewById(R.id.button_decrement);

        Button buttonIncrement =
                findViewById(R.id.button_increment);

        buttonDecrement.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {
                        decrement();
                    }
                });

        buttonIncrement.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {
                        increment();
                    }
                });

        if (savedInstanceState != null) {

            count = savedInstanceState.getInt("count");

            textViewCount.setText(
                    String.valueOf(count)
            );
        }
    }

    private void increment() {

        count++;

        textViewCount.setText(
                String.valueOf(count)
        );
    }

    private void decrement() {

        count--;

        textViewCount.setText(
                String.valueOf(count)
        );
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {

        super.onSaveInstanceState(outState);

        outState.putInt("count", count);
    }
}

How This Implementation Works

The workflow is:

  1. User changes counter value
  2. Android destroys Activity during rotation
  3. onSaveInstanceState() stores counter value
  4. Activity recreates automatically
  5. Saved value restores inside onCreate()

How Android Lifecycle Handles State

When configuration changes happen:


onPause()
↓
onStop()
↓
onSaveInstanceState()
↓
onDestroy()
↓
onCreate()
↓
onStart()
↓
onResume()

This lifecycle allows Android to preserve temporary UI data.


What Type of Data Should Be Saved?

Good examples:

  • Counter values
  • Temporary form data
  • Selected tabs
  • Scroll positions
  • Search queries

What Should NOT Be Saved?

Avoid storing:

  • Large images
  • Database objects
  • Huge collections
  • Heavy serialized objects

Large Bundle data can cause performance issues.


Common Mistakes Developers Make

1. Forgetting Null Checks

Always check:


if (savedInstanceState != null)

before restoring values.


2. Saving Large Data

Bundles are intended for lightweight UI state only.


3. Not Updating UI After Restore

Restoring variables without updating UI components causes incorrect screen states.


Modern Android Recommendations

Modern Android applications often use:

  • ViewModel
  • SavedStateHandle
  • Jetpack Compose state management
  • MVVM architecture

These approaches improve lifecycle-aware state management.


ViewModel vs onSaveInstanceState

ViewModel onSaveInstanceState
Survives configuration changes Stores temporary UI state
Better for business logic Better for small UI data
Lifecycle aware Bundle based

FAQ

Why does Android recreate Activities during rotation?

Android recreates Activities to reload resources for the new configuration.

Can onSaveInstanceState save large objects?

No. It should only store lightweight temporary UI data.

What is the modern alternative to this approach?

ViewModel and SavedStateHandle are modern lifecycle-aware solutions.


Conclusion

Handling Activity state correctly is an important part of Android development.

Using onSaveInstanceState() helps preserve temporary UI data during configuration changes and improves user experience.

Modern Android applications should combine lifecycle-aware architecture with proper state management techniques.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, fintech applications, scalable SaaS systems, and developer tooling platforms.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Saturday, September 26, 2020

How to Use AsyncTask in Android for Background Operations

Android applications should never perform heavy operations on the main UI thread.

Tasks such as:

  • Network requests
  • File downloads
  • Database processing
  • Large calculations

can block the UI and make applications unresponsive.

In this tutorial, we will learn how to use AsyncTask in Android to perform background operations while updating the UI safely.


What Is AsyncTask?

AsyncTask is an Android class used to perform background operations and publish results on the UI thread.

It helps developers avoid manually managing:

  • Threads
  • Handlers
  • Runnable objects

Although AsyncTask is deprecated in modern Android development, understanding it is still useful for maintaining older Android projects.


How AsyncTask Works

An AsyncTask typically contains four important methods:

Method Purpose
onPreExecute() Runs before background task starts
doInBackground() Performs heavy background work
onProgressUpdate() Updates progress on UI thread
onPostExecute() Returns result to UI thread

What We Will Build

In this example:

  • User clicks a button
  • Background task starts
  • ProgressBar updates gradually
  • Toast message appears after completion

Step 1 — Create Layout File

Create the UI inside activity_main.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:gravity="center"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progress_bar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:visibility="invisible"/>

    <Button
        android:id="@+id/button_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"/>

</LinearLayout>

Step 2 — Implement MainActivity.java

Open MainActivity.java and add the following code:


package com.example.asynctaskexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        progressBar = findViewById(R.id.progress_bar);

        Button buttonStart =
                findViewById(R.id.button_start);

        buttonStart.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {

                        ExampleAsyncTask task =
                                new ExampleAsyncTask(
                                        MainActivity.this
                                );

                        task.execute(10);
                    }
                });
    }

    private static class ExampleAsyncTask
            extends AsyncTask<Integer, Integer, String> {

        private WeakReference<MainActivity>
                activityWeakReference;

        ExampleAsyncTask(MainActivity activity) {

            activityWeakReference =
                    new WeakReference<>(activity);
        }

        @Override
        protected void onPreExecute() {

            super.onPreExecute();

            MainActivity activity =
                    activityWeakReference.get();

            if (activity == null
                    || activity.isFinishing()) {

                return;
            }

            activity.progressBar
                    .setVisibility(View.VISIBLE);
        }

        @Override
        protected String doInBackground(
                Integer... integers
        ) {

            for (int i = 0;
                 i < integers[0];
                 i++) {

                publishProgress(
                        (i * 100) / integers[0]
                );

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
            }

            return "Finished!";
        }

        @Override
        protected void onProgressUpdate(
                Integer... values
        ) {

            super.onProgressUpdate(values);

            MainActivity activity =
                    activityWeakReference.get();

            if (activity == null
                    || activity.isFinishing()) {

                return;
            }

            activity.progressBar
                    .setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(String s) {

            super.onPostExecute(s);

            MainActivity activity =
                    activityWeakReference.get();

            if (activity == null
                    || activity.isFinishing()) {

                return;
            }

            Toast.makeText(
                    activity,
                    s,
                    Toast.LENGTH_SHORT
            ).show();

            activity.progressBar.setProgress(0);

            activity.progressBar
                    .setVisibility(View.INVISIBLE);
        }
    }
}

Why WeakReference Is Important

AsyncTask can continue running even after the Activity is destroyed.

If developers keep a strong Activity reference, memory leaks can happen.

Using:


WeakReference<MainActivity>

helps prevent memory leaks by allowing Android to clean destroyed Activities properly.


How This Example Works

The workflow is:

  1. User clicks Start button
  2. AsyncTask starts in background
  3. Progress updates every second
  4. ProgressBar updates on UI thread
  5. Toast appears after completion

Understanding AsyncTask Generic Types


AsyncTask<Params, Progress, Result>
Type Purpose
Params Input parameters
Progress Progress updates
Result Final task result

Common Mistakes Developers Make

1. Updating UI Inside doInBackground()

UI updates should only happen on the main thread.


2. Creating Memory Leaks

Holding strong Activity references can leak destroyed Activities.


3. Running Heavy Infinite Tasks

Long-running operations should use WorkManager or background services instead.


Why AsyncTask Is Deprecated

Google deprecated AsyncTask because:

  • Lifecycle handling is difficult
  • Memory leaks are common
  • Limited threading flexibility
  • Poor scalability

Modern Alternatives to AsyncTask

Modern Android applications should use:

  • Coroutines
  • WorkManager
  • RxJava
  • Executors
  • LiveData + ViewModel

AsyncTask vs Coroutines

AsyncTask Coroutines
Deprecated Modern solution
Complex lifecycle handling Lifecycle-aware support
More boilerplate Cleaner asynchronous code

FAQ

Is AsyncTask still useful in 2026?

Mainly for maintaining older Android projects and interview preparation.

Why should heavy work avoid the UI thread?

Heavy operations can freeze the UI and cause ANR (Application Not Responding) errors.

What is the best replacement for AsyncTask?

Kotlin Coroutines and WorkManager are modern recommended solutions.


Conclusion

AsyncTask helped Android developers perform background operations more easily in older Android applications.

Although deprecated today, understanding AsyncTask is still useful for maintaining legacy projects and understanding Android threading concepts.

Modern Android applications should prefer lifecycle-aware asynchronous solutions such as Coroutines and WorkManager.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS platforms, fintech systems, and developer tooling applications.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Saturday, September 19, 2020

How to Create a Modal Bottom Sheet in Android Using BottomSheetDialogFragment

Bottom Sheets are one of the most commonly used UI components in modern Android applications.

Applications use Bottom Sheets for:

  • Action menus
  • Filters
  • Payment options
  • Sharing dialogs
  • User interactions

In this tutorial, we will learn how to create a Modal Bottom Sheet in Android using BottomSheetDialogFragment.


What Is a Modal Bottom Sheet?

A Modal Bottom Sheet is a UI component that slides up from the bottom of the screen and temporarily blocks interaction with the underlying content.

It is commonly used to display:

  • Quick actions
  • Options menus
  • Confirmation dialogs
  • Interactive forms

What We Will Build

In this example:

  • User clicks a button
  • Modal Bottom Sheet opens
  • User selects an option
  • Bottom Sheet communicates back to the Activity

Step 1 — Add Material Dependency

Open your build.gradle file and add:


implementation 'com.google.android.material:material:1.11.0'

Then sync your Gradle project.


Step 2 — Create activity_main.xml

Create the main layout file.


<?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:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/button_open_bottom_sheet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Modal Bottom Sheet"/>

    <TextView
        android:id="@+id/text_view_button_clicked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Button X clicked"
        android:textSize="22sp"/>

</LinearLayout>

Step 3 — Create Bottom Sheet Layout

Create bottom_sheet_layout.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="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is a Bottom Sheet"
        android:textSize="22sp"/>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"/>

</LinearLayout>

Step 4 — Create ExampleBottomSheetDialog.java

Create a new Java class named ExampleBottomSheetDialog.java.


package com.example.bottomsheetexample;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.material.bottomsheet.BottomSheetDialogFragment;

public class ExampleBottomSheetDialog
        extends BottomSheetDialogFragment {

    private BottomSheetListener mListener;

    @Nullable
    @Override
    public View onCreateView(
            @NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState
    ) {

        View view = inflater.inflate(
                R.layout.bottom_sheet_layout,
                container,
                false
        );

        Button button1 =
                view.findViewById(R.id.button1);

        Button button2 =
                view.findViewById(R.id.button2);

        button1.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        mListener.onButtonClicked(
                                "Button 1 clicked"
                        );

                        dismiss();
                    }
                });

        button2.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        mListener.onButtonClicked(
                                "Button 2 clicked"
                        );

                        dismiss();
                    }
                });

        return view;
    }

    public interface BottomSheetListener {

        void onButtonClicked(String text);
    }

    @Override
    public void onAttach(Context context) {

        super.onAttach(context);

        try {

            mListener =
                    (BottomSheetListener) context;

        } catch (ClassCastException e) {

            throw new ClassCastException(
                    context.toString()
                            + " must implement BottomSheetListener"
            );
        }
    }
}

Step 5 — Implement MainActivity.java

Open MainActivity.java and add the following code.


package com.example.bottomsheetexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
        implements ExampleBottomSheetDialog.BottomSheetListener {

    private TextView textViewResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        textViewResult =
                findViewById(
                        R.id.text_view_button_clicked
                );

        Button buttonOpenBottomSheet =
                findViewById(
                        R.id.button_open_bottom_sheet
                );

        buttonOpenBottomSheet.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        ExampleBottomSheetDialog bottomSheet =
                                new ExampleBottomSheetDialog();

                        bottomSheet.show(
                                getSupportFragmentManager(),
                                "exampleBottomSheet"
                        );
                    }
                });
    }

    @Override
    public void onButtonClicked(String text) {

        textViewResult.setText(text);
    }
}

How This Implementation Works

The workflow is:

  1. User clicks button
  2. Bottom Sheet appears from bottom
  3. User selects an option
  4. Bottom Sheet sends callback to Activity
  5. Activity updates UI

Why Interfaces Are Used

The Bottom Sheet communicates with the Activity using an interface.

This approach:

  • Reduces tight coupling
  • Improves modularity
  • Makes components reusable

Common Mistakes Developers Make

1. Forgetting Material Dependency

Without Material Components dependency, BottomSheetDialogFragment will not work properly.


2. Context Casting Errors

The Activity must implement the BottomSheetListener interface.


3. Using match_parent Height

Bottom Sheets should generally use wrap_content height for better UI behavior.


Modern Android Improvements

Modern Android applications can improve this implementation using:

  • Kotlin
  • Jetpack Compose
  • ViewBinding
  • MVVM architecture
  • Material 3 Bottom Sheets

Bottom Sheet vs Dialog

Bottom Sheet Dialog
Slides from bottom Appears in center
Modern mobile UX Traditional popup UI
Gesture-friendly Less interactive

FAQ

What is the difference between modal and persistent Bottom Sheets?

Modal Bottom Sheets block interaction temporarily, while persistent Bottom Sheets remain visible alongside app content.

Can Bottom Sheets contain RecyclerViews?

Yes. Bottom Sheets can contain complex layouts including RecyclerViews and forms.

Should Bottom Sheets be used instead of dialogs?

For modern Android mobile UX, Bottom Sheets are often preferred over traditional dialogs.


Conclusion

Bottom Sheets are an important part of modern Android UI design.

Using BottomSheetDialogFragment allows developers to create interactive and user-friendly modal components with clean communication patterns.

Modern Android applications should combine Material Design components with lifecycle-aware architecture for scalable UI development.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS platforms, fintech systems, and developer tooling applications.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Wednesday, September 2, 2020

How to Create a Custom Calendar Date Picker in Android Using TimeSquare Library

Date selection is a common feature in modern Android applications.

Applications use calendar pickers for:

  • Booking systems
  • Travel applications
  • Task scheduling
  • Event management
  • Hotel reservations

Although Android provides a default DatePickerDialog, many developers prefer custom calendar views for better user experience and UI flexibility.

In this tutorial, we will learn how to create a custom Calendar Date Picker in Android using the TimeSquare library.


What Is TimeSquare Library?

TimeSquare is an Android calendar library developed by Square.

It provides:

  • Custom calendar UI
  • Date range selection
  • Single date selection
  • Customizable calendar views

Features of CalendarPickerView

  • Range selection support
  • Minimum and maximum date support
  • Custom date formatting
  • Modern calendar interface
  • Multiple date selection

Step 1 — Add Dependency

Open your build.gradle file and add:


implementation 'com.squareup:android-times-square:1.6.5@aar'

Then sync your Gradle project.


Step 2 — Create activity_main.xml

Create the layout file.


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

<androidx.constraintlayout.widget.ConstraintLayout
    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">

    <com.squareup.timessquare.CalendarPickerView
        android:id="@+id/calendar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3 — Implement MainActivity.java

Open MainActivity.java and add the following code.


package com.example.timesquaredatepicker;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Toast;

import com.squareup.timessquare.CalendarPickerView;

import java.util.Calendar;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Date today = new Date();

        Calendar nextYear =
                Calendar.getInstance();

        nextYear.add(Calendar.YEAR, 1);

        CalendarPickerView datePicker =
                findViewById(R.id.calendar);

        datePicker.init(
                today,
                nextYear.getTime()
        )
        .inMode(
                CalendarPickerView.SelectionMode.RANGE
        )
        .withSelectedDate(today);

        datePicker.setOnDateSelectedListener(
                new CalendarPickerView
                        .OnDateSelectedListener() {

                    @Override
                    public void onDateSelected(Date date) {

                        Calendar selectedCalendar =
                                Calendar.getInstance();

                        selectedCalendar.setTime(date);

                        String selectedDate =
                                selectedCalendar.get(
                                        Calendar.DAY_OF_MONTH
                                )
                                + "/"
                                + (
                                selectedCalendar.get(
                                        Calendar.MONTH
                                ) + 1
                        )
                                + "/"
                                + selectedCalendar.get(
                                        Calendar.YEAR
                        );

                        Toast.makeText(
                                MainActivity.this,
                                selectedDate,
                                Toast.LENGTH_SHORT
                        ).show();
                    }

                    @Override
                    public void onDateUnselected(Date date) {
                    }
                });
    }
}

How This Implementation Works

The workflow is:

  1. Calendar initializes with current date
  2. Maximum range extends to next year
  3. User selects dates
  4. Listener detects selected date
  5. Selected date displays using Toast

Understanding Selection Modes

Selection Mode Description
SINGLE Select only one date
RANGE Select multiple dates as a range
MULTIPLE Select multiple independent dates

How Date Formatting Works

The selected date is converted using the Calendar class.


Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

This helps extract:

  • Day
  • Month
  • Year

How to Get Selected Date Range

For range selection, developers can retrieve selected dates using:


List<Date> selectedDates =
        datePicker.getSelectedDates();

This is useful for booking and reservation systems.


Common Mistakes Developers Make

1. Invalid Date Range

Minimum date should always be before maximum date.


2. Forgetting Date Formatting

Raw Date objects are difficult for users to understand.


3. Large Calendar Ranges

Very large date ranges may affect performance.


Modern Android Improvements

Modern Android applications can improve this implementation using:

  • Kotlin
  • Material 3 Date Pickers
  • Jetpack Compose
  • MVVM architecture
  • ViewBinding

TimeSquare vs Default DatePickerDialog

TimeSquare DatePickerDialog
Custom calendar UI Default Android UI
Supports range selection Single date only
More flexible customization Limited UI customization

FAQ

Can TimeSquare select multiple dates?

Yes. It supports single, multiple, and range date selection modes.

Is TimeSquare still useful in 2026?

Yes, especially for maintaining legacy Android projects and creating custom calendar experiences.

What is the modern alternative?

Material 3 Date Pickers and Jetpack Compose date components are modern alternatives.


Conclusion

Custom calendar pickers improve user experience significantly in Android applications.

The TimeSquare library provides flexible date selection functionality with support for custom calendar views and date ranges.

Modern Android applications should combine lifecycle-aware architecture with modern Material Design components for scalable UI development.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS platforms, fintech systems, and developer tooling applications.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Wednesday, August 26, 2020

How to Build a Countdown Timer in Android Using CountDownTimer

Countdown timers are commonly used in Android applications for:

  • OTP verification screens
  • Quiz applications
  • Workout timers
  • Pomodoro productivity apps
  • Game countdown systems

Android provides a built-in class called CountDownTimer that helps developers implement countdown functionality easily.

In this tutorial, we will learn how to build a Countdown Timer in Android using the CountDownTimer class.


What Is CountDownTimer?

CountDownTimer is an Android utility class used to create timers that count down over a fixed interval.

It provides two important callback methods:

  • onTick() → Called at regular intervals
  • onFinish() → Called when timer completes

What We Will Build

In this Android example:

  • User can start the timer
  • User can pause the timer
  • User can reset the timer
  • Remaining time updates dynamically

Step 1 — Create activity_main.xml

Create the UI layout file.


<?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="match_parent">

    <TextView
        android:id="@+id/text_view_countdown"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="10:00"
        android:textColor="@android:color/black"
        android:textSize="60sp"/>

    <Button
        android:id="@+id/button_start_pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_view_countdown"
        android:layout_centerHorizontal="true"
        android:text="Start"/>

    <Button
        android:id="@+id/button_reset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text_view_countdown"
        android:layout_marginStart="12dp"
        android:layout_toEndOf="@id/button_start_pause"
        android:text="Reset"
        android:visibility="gone"/>

</RelativeLayout>

Step 2 — Implement MainActivity.java

Open MainActivity.java and add the following code.


package com.example.countdowntimer;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private static final long START_TIME_IN_MILLIS =
            600000;

    private TextView textViewCountDown;

    private Button buttonStartPause;

    private Button buttonReset;

    private CountDownTimer countDownTimer;

    private boolean timerRunning;

    private long timeLeftInMillis =
            START_TIME_IN_MILLIS;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        textViewCountDown =
                findViewById(R.id.text_view_countdown);

        buttonStartPause =
                findViewById(R.id.button_start_pause);

        buttonReset =
                findViewById(R.id.button_reset);

        buttonStartPause.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {

                        if (timerRunning) {

                            pauseTimer();

                        } else {

                            startTimer();
                        }
                    }
                });

        buttonReset.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {

                        resetTimer();
                    }
                });

        updateCountDownText();
    }

    private void startTimer() {

        countDownTimer =
                new CountDownTimer(
                        timeLeftInMillis,
                        1000
                ) {

                    @Override
                    public void onTick(
                            long millisUntilFinished
                    ) {

                        timeLeftInMillis =
                                millisUntilFinished;

                        updateCountDownText();
                    }

                    @Override
                    public void onFinish() {

                        timerRunning = false;

                        buttonStartPause.setText(
                                "Start"
                        );

                        buttonStartPause.setVisibility(
                                View.INVISIBLE
                        );

                        buttonReset.setVisibility(
                                View.VISIBLE
                        );
                    }
                }.start();

        timerRunning = true;

        buttonStartPause.setText("Pause");

        buttonReset.setVisibility(View.INVISIBLE);
    }

    private void pauseTimer() {

        countDownTimer.cancel();

        timerRunning = false;

        buttonStartPause.setText("Start");

        buttonReset.setVisibility(View.VISIBLE);
    }

    private void resetTimer() {

        timeLeftInMillis =
                START_TIME_IN_MILLIS;

        updateCountDownText();

        buttonReset.setVisibility(View.INVISIBLE);

        buttonStartPause.setVisibility(View.VISIBLE);
    }

    private void updateCountDownText() {

        int minutes =
                (int) (timeLeftInMillis / 1000) / 60;

        int seconds =
                (int) (timeLeftInMillis / 1000) % 60;

        String timeFormatted =
                String.format(
                        Locale.getDefault(),
                        "%02d:%02d",
                        minutes,
                        seconds
                );

        textViewCountDown.setText(timeFormatted);
    }
}

How This Countdown Timer Works

The workflow is:

  1. User clicks Start button
  2. CountDownTimer begins counting
  3. onTick() updates remaining time every second
  4. User can pause or reset timer
  5. onFinish() triggers after countdown completes

Understanding CountDownTimer Parameters


new CountDownTimer(totalTime, interval)
Parameter Description
totalTime Total countdown duration
interval Update interval frequency

How Time Formatting Works

The timer stores values in milliseconds.

We convert milliseconds into:

  • Minutes
  • Seconds

Then format the output using:


String.format("%02d:%02d", minutes, seconds)

Common Mistakes Developers Make

1. Forgetting to Cancel Timer

Timers should be cancelled when no longer needed to avoid memory leaks.


2. Updating UI Incorrectly

UI updates should happen safely inside timer callbacks.


3. Not Handling Activity Lifecycle

Timers may continue running during Activity destruction if lifecycle handling is ignored.


Modern Android Improvements

Modern Android applications can improve this implementation using:

  • Kotlin
  • Coroutines
  • Jetpack Compose
  • ViewModel
  • Lifecycle-aware timers

CountDownTimer vs Coroutines

CountDownTimer Coroutines
Simple built-in solution Modern asynchronous approach
Limited lifecycle support Lifecycle-aware architecture
Good for basic timers Better for scalable apps

FAQ

Can CountDownTimer run in background?

CountDownTimer is tied to the application process and is not ideal for long-running background timers.

Can I create custom intervals?

Yes. Developers can update timers at any interval such as 500ms or 1 second.

What is the modern alternative?

Kotlin Coroutines combined with ViewModel is the preferred modern solution.


Conclusion

Countdown timers are useful for many Android application features including OTP systems, quizzes, and productivity apps.

The CountDownTimer class provides a simple way to implement countdown functionality with regular UI updates.

Modern Android applications should combine timers with lifecycle-aware architecture for better scalability and reliability.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, scalable SaaS systems, fintech applications, and developer tooling platforms.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Tuesday, August 25, 2020

How to Create a Circular Progress Bar in Android Studio

Circular progress bars are commonly used in Android applications to display progress visually in a modern and interactive way.

Examples include:

  • File download progress
  • Fitness tracking
  • Loading indicators
  • Battery percentage
  • Task completion status
  • Dashboard analytics

In this tutorial, we will learn how to create a customizable circular progress bar in Android Studio using:

  • Custom drawable XML
  • Gradient progress effect
  • ProgressBar widget
  • Dynamic progress updates

What We Will Build

In this Android example:

  • Create circular progress bar
  • Add gradient effect
  • Display percentage text
  • Increase and decrease progress dynamically
  • Create modern UI using ConstraintLayout

Step 1 — Create circle.xml Drawable

Create:


res/drawable/circle.xml

Add the following code:


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

<layer-list
    xmlns:android=
    "http://schemas.android.com/apk/res/android">

    <item>

        <shape
            android:shape="ring"
            android:thicknessRatio="16"
            android:useLevel="false">

            <solid
                android:color="#DDDDDD"/>

        </shape>

    </item>

    <item>

        <rotate
            android:fromDegrees="270"
            android:toDegrees="270">

            <shape
                android:shape="ring"
                android:thicknessRatio="16"
                android:useLevel="true">

                <gradient
                    android:startColor=
                    "@color/purple_500"

                    android:endColor=
                    "@color/teal_200"

                    android:type="sweep"/>

            </shape>

        </rotate>

    </item>

</layer-list>

How This Drawable Works

This drawable creates:

  • Gray circular background ring
  • Gradient progress ring
  • Sweep animation effect
  • Circular determinate progress style

Step 2 — Add Circular Progress Style

Inside:


res/values/styles.xml

Add:


<style
    name="CircularDeterminateProgressBar">

    <item
        name="android:indeterminateOnly">
        false
    </item>

    <item
        name="android:progressDrawable">
        @drawable/circle
    </item>

    <item
        name="android:indeterminateDrawable">
        @drawable/circle
    </item>

    <item
        name="android:minHeight">
        200dp
    </item>

    <item
        name="android:maxHeight">
        200dp
    </item>

</style>

Step 3 — Create activity_main.xml

Create:


res/layout/activity_main.xml

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

<androidx.constraintlayout.widget.ConstraintLayout

    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">

    <ProgressBar
        android:id="@+id/progress_bar"

        style=
        "@style/CircularDeterminateProgressBar"

        android:layout_width="200dp"

        android:layout_height="200dp"

        android:max="100"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        tools:progress="60"/>

    <TextView
        android:id="@+id/text_view_progress"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textSize="28sp"

        android:textStyle="bold"

        app:layout_constraintTop_toTopOf=
        "@id/progress_bar"

        app:layout_constraintBottom_toBottomOf=
        "@id/progress_bar"

        app:layout_constraintStart_toStartOf=
        "@id/progress_bar"

        app:layout_constraintEnd_toEndOf=
        "@id/progress_bar"

        tools:text="60%"/>

    <Button
        android:id="@+id/button_decr"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="-10%"

        app:layout_constraintTop_toBottomOf=
        "@id/progress_bar"

        app:layout_constraintStart_toStartOf=
        "@id/progress_bar"/>

    <Button
        android:id="@+id/button_incr"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="+10%"

        app:layout_constraintTop_toBottomOf=
        "@id/progress_bar"

        app:layout_constraintEnd_toEndOf=
        "@id/progress_bar"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4 — Create MainActivity.java

Create:


MainActivity.java

package com.example.circularprogressbar;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity
        extends AppCompatActivity {

    private Button buttonIncr;

    private Button buttonDecr;

    private ProgressBar progressBar;

    private TextView textViewProgress;

    private int progress = 0;

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        buttonIncr =
                findViewById(R.id.button_incr);

        buttonDecr =
                findViewById(R.id.button_decr);

        progressBar =
                findViewById(R.id.progress_bar);

        textViewProgress =
                findViewById(
                        R.id.text_view_progress
                );

        updateProgressBar();

        buttonIncr.setOnClickListener(
                new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                if (progress <= 90) {

                    progress += 10;

                    updateProgressBar();
                }
            }
        });

        buttonDecr.setOnClickListener(
                new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                if (progress >= 10) {

                    progress -= 10;

                    updateProgressBar();
                }
            }
        });
    }

    private void updateProgressBar() {

        progressBar.setProgress(progress);

        textViewProgress.setText(
                progress + "%"
        );
    }
}

How This Circular Progress Bar Works

The workflow is:

  1. Custom ring drawable creates circular shape
  2. Gradient sweep displays progress color
  3. ProgressBar updates dynamically
  4. TextView shows current percentage
  5. Buttons control progress changes

Why Use Circular Progress Bars?

Circular progress indicators:

  • Look modern
  • Improve user experience
  • Save UI space
  • Provide visual feedback clearly

Modern Android Recommendations

Modern Android applications often prefer:

  • Material Progress Indicators
  • Jetpack Compose progress components
  • Lottie animations
  • Animated vector drawables

Material Design Circular Indicator

Google Material Design now recommends:


CircularProgressIndicator

from Material Components library for modern applications.


Common Beginner Mistakes

1. Forgetting max Value

Always set:


android:max="100"

2. Using Large Thickness Ratio

Very thick rings may distort the UI.


3. Updating Progress Outside Main Thread

UI updates must occur on the main thread.


ProgressBar vs CircularProgressIndicator

ProgressBar CircularProgressIndicator
Classic Android widget Material Design component
Custom drawable required Modern styling built-in
Flexible customization Better Material integration

Advanced Customization Ideas

  • Animated progress transitions
  • Gradient animations
  • Custom fonts
  • Percentage labels
  • Loading animations
  • Dynamic colors

FAQ

Can circular progress bars animate smoothly?

Yes. ObjectAnimator and ValueAnimator can animate progress updates smoothly.

Can gradient colors be customized?

Yes. Modify:


android:startColor
android:endColor

inside the drawable XML.

What is the modern recommended approach?

Material CircularProgressIndicator or Jetpack Compose progress indicators are recommended for modern Android applications.


Conclusion

Circular progress bars provide visually attractive and interactive progress indicators for Android applications.

Using custom drawable XML with ProgressBar allows developers to build highly customizable circular indicators with gradients and animations.

Modern Android applications should combine Material Design components, smooth animations, and lifecycle-aware updates for professional UI experiences.


About the Author

Salil Jha is a Full Stack and Mobile Developer specializing in Android, React Native, fintech systems, scalable SaaS platforms, and developer tooling products.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Monday, August 10, 2020

How to Implement Bottom Navigation with Fragments in Android

Bottom Navigation is one of the most widely used navigation patterns in modern Android applications.

Applications like:

  • Instagram
  • YouTube
  • Spotify
  • Facebook
  • WhatsApp

all use bottom navigation for switching between major sections of the app.

In this tutorial, we will learn how to create a Bottom Navigation system in Android using:

  • BottomNavigationView
  • Fragments
  • Fragment transactions
  • AndroidX libraries
  • Modern Material Design components

What We Will Build

In this Android example:

  • Create BottomNavigationView
  • Add 3 navigation tabs
  • Switch between Fragments
  • Use FragmentManager
  • Preserve selected tab during rotation

Why Use Bottom Navigation?

Bottom Navigation improves:

  • User experience
  • App navigation
  • Accessibility
  • Mobile usability

It is best suited for applications with:

  • 3 to 5 primary sections
  • Frequently accessed screens
  • Simple navigation hierarchy

Step 1 — Add Material Dependency

Inside:


build.gradle

Add:


implementation
"com.google.android.material:material:1.11.0"

Step 2 — Create Menu Resource

Create:


res/menu/bottom_navigation.xml

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

<menu
    xmlns:android=
    "http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/nav_home"
        android:icon="@drawable/ic_home"
        android:title="Home"/>

    <item
        android:id="@+id/nav_favorites"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"/>

    <item
        android:id="@+id/nav_search"
        android:icon="@drawable/ic_search"
        android:title="Search"/>

</menu>

Step 3 — Create activity_main.xml

Create:


res/layout/activity_main.xml

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

<androidx.constraintlayout.widget.ConstraintLayout

    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">

    <FrameLayout
        android:id="@+id/fragment_container"

        android:layout_width="0dp"

        android:layout_height="0dp"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintBottom_toTopOf=
        "@id/bottom_navigation"

        app:layout_constraintStart_toStartOf=
        "parent"

        app:layout_constraintEnd_toEndOf=
        "parent"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView

        android:id="@+id/bottom_navigation"

        android:layout_width="0dp"

        android:layout_height="wrap_content"

        android:background="?android:attr/windowBackground"

        app:menu="@menu/bottom_navigation"

        app:layout_constraintBottom_toBottomOf=
        "parent"

        app:layout_constraintStart_toStartOf=
        "parent"

        app:layout_constraintEnd_toEndOf=
        "parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4 — Create Home Fragment Layout

Create:


res/layout/fragment_home.xml

<RelativeLayout
    xmlns:android=
    "http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:background=
    "@android:color/holo_red_light">

    <TextView
        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Home Fragment"

        android:textSize="30sp"

        android:layout_centerInParent="true"/>

</RelativeLayout>

Step 5 — Create HomeFragment.java


package com.example.bottomnavigation;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class HomeFragment
        extends Fragment {

    @Nullable
    @Override
    public View onCreateView(
            @NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState
    ) {

        return inflater.inflate(
                R.layout.fragment_home,
                container,
                false
        );
    }
}

Step 6 — Create Favorites Fragment

Create:


fragment_favorites.xml
FavoritesFragment.java

Update text and background accordingly.


Step 7 — Create Search Fragment

Create:


fragment_search.xml
SearchFragment.java

Update text and background accordingly.


Step 8 — Create MainActivity.java


package com.example.bottomnavigation;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;

import android.os.Bundle;
import android.view.MenuItem;

import com.google.android.material.bottomnavigation.BottomNavigationView;

public class MainActivity
        extends AppCompatActivity {

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        BottomNavigationView bottomNav =
                findViewById(
                        R.id.bottom_navigation
                );

        bottomNav.setOnItemSelectedListener(
                navListener
        );

        if (savedInstanceState == null) {

            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(
                            R.id.fragment_container,
                            new HomeFragment()
                    )
                    .commit();
        }
    }

    private final
    BottomNavigationView.OnItemSelectedListener
            navListener =
            new BottomNavigationView
                    .OnItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(
                @NonNull MenuItem item
        ) {

            Fragment selectedFragment =
                    null;

            int id = item.getItemId();

            if (id == R.id.nav_home) {

                selectedFragment =
                        new HomeFragment();

            } else if (
                    id == R.id.nav_favorites
            ) {

                selectedFragment =
                        new FavoritesFragment();

            } else if (
                    id == R.id.nav_search
            ) {

                selectedFragment =
                        new SearchFragment();
            }

            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(
                            R.id.fragment_container,
                            selectedFragment
                    )
                    .commit();

            return true;
        }
    };
}

How This Bottom Navigation Works

The workflow is:

  1. User taps navigation item
  2. Listener detects selected menu
  3. Corresponding Fragment is created
  4. FragmentTransaction replaces container
  5. Selected screen becomes visible

Why Use Fragments with Bottom Navigation?

Fragments improve:

  • Navigation flexibility
  • Screen reusability
  • Memory efficiency
  • Large-screen support

Modern Android Recommendations

Modern Android applications now prefer:

  • Navigation Component
  • Jetpack Compose Navigation
  • Single Activity Architecture
  • ViewBinding
  • MVVM Architecture

BottomNavigationView vs Navigation Component

Classic Fragment Navigation Navigation Component
Manual fragment transactions Automatic navigation handling
More boilerplate code Cleaner architecture
Basic implementation Modern recommended approach

Common Beginner Mistakes

1. Using Old Support Libraries

Always use:


androidx

instead of deprecated:


android.support

2. Recreating Fragments Repeatedly

Large applications should cache fragments for better performance.


3. Forgetting savedInstanceState Check

Without:


if (savedInstanceState == null)

Fragments may overlap during screen rotation.


Advanced Improvements

  • Add badges
  • Use animations
  • Integrate ViewPager2
  • Add deep linking
  • Use Safe Args
  • Implement shared ViewModel

FAQ

How many items should Bottom Navigation contain?

Material Design recommends 3 to 5 navigation items.

Should Bottom Navigation use Activities or Fragments?

Fragments are preferred for modern Android applications.

What is the modern recommended navigation system?

Navigation Component with Single Activity Architecture is recommended for scalable Android applications.


Conclusion

Bottom Navigation provides a clean and user-friendly navigation experience for Android applications.

Using BottomNavigationView with Fragments allows developers to build scalable multi-screen applications with smooth navigation.

Modern Android applications should combine Bottom Navigation with Navigation Component, lifecycle-aware architecture, and Material Design principles for production-grade navigation systems.


About the Author

Salil Jha is a Full Stack and Mobile Developer specializing in Android, React Native, fintech systems, scalable SaaS platforms, and developer tooling products.

CodeChain Dev — Build Modern Products. Solve Real Problems.