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

Sunday, January 5, 2025

How Ethereum Smart Contract Deposits Work in Modern Crypto Platforms

Ethereum smart contracts are one of the most important technologies in modern blockchain systems.

They enable decentralized applications (dApps), automated payments, token systems, staking platforms, and decentralized finance (DeFi) protocols.

However, smart contract transactions also create challenges for crypto exchanges, wallets, and payment tracking systems.

In this article, we will explore how Ethereum smart contract deposits work, why exchanges sometimes reject them, and how modern platforms handle blockchain payment tracking in 2026.


What Is a Smart Contract?

A smart contract is a self-executing program deployed on the Ethereum blockchain.

It automatically performs actions when predefined conditions are met.

Smart contracts are commonly used for:

  • Token transfers
  • Decentralized exchanges
  • NFT marketplaces
  • Staking systems
  • Automated payments

Direct ETH Transfers vs Smart Contract Transfers

Direct ETH Transfer

A direct Ethereum transaction is simple:


Wallet A → Wallet B

The blockchain clearly records:

  • Sender address
  • Receiver address
  • Transfer amount

This makes deposit tracking straightforward.


Smart Contract Transfer

Smart contract transactions work differently.

Instead of directly transferring ETH between wallets, the transaction interacts with a contract.

Example:


Wallet A → Smart Contract → Wallet B

The transfer may happen internally during contract execution.

This makes payment tracking more complicated for exchanges and wallet systems.


Why Exchanges Sometimes Reject Smart Contract Deposits

Many exchanges assign unique Ethereum deposit addresses to users.

Their backend systems monitor these addresses for incoming deposits.

However, smart contract deposits can be difficult to identify because:

  • Transfers may happen internally
  • Transaction logs can be complex
  • Some systems only monitor direct transfers
  • Internal contract calls require additional parsing

As a result, some exchanges display warnings such as:

Do not send funds from smart contracts.


Common Deposit Tracking Methods

1. Direct Transaction Monitoring

The simplest method is monitoring direct ETH transfers.

Example:


from: 0x123...
to: 0x456...
value: 1 ETH

This method is fast and efficient.

However, it may fail to detect complex smart contract interactions.


2. Balance Difference Tracking

Some systems monitor wallet balances after every block.

Formula:

:contentReference[oaicite:1]{index=1}

This approach detects incoming funds even if the transfer is hidden inside smart contract execution.

But it also creates limitations:

  • Higher infrastructure cost
  • More blockchain requests
  • Difficult sender identification
  • Problems during simultaneous deposits

3. Event Log Parsing

Modern blockchain systems often parse smart contract events.

Ethereum contracts emit logs such as:


Transfer(address from, address to, uint256 amount)

Backend systems scan these events to identify deposits accurately.

This method is widely used by modern exchanges and blockchain analytics systems.


Challenges in Smart Contract Deposit Tracking

  • Internal transactions are difficult to trace
  • High blockchain indexing costs
  • Large transaction volume
  • Complex contract execution paths
  • Node synchronization delays

These issues become more significant for large exchanges handling thousands of transactions per minute.


How Modern Platforms Handle Ethereum Deposits

Modern crypto platforms use advanced infrastructure such as:

  • Blockchain indexers
  • Event parsers
  • WebSocket listeners
  • Real-time monitoring systems
  • Transaction confirmation queues

Popular blockchain infrastructure providers include:

  • Alchemy
  • Infura
  • QuickNode

These services help platforms track deposits more efficiently.


Security Considerations

Blockchain payment systems must also handle:

  • Double-spend prevention
  • Re-org handling
  • Pending transaction monitoring
  • Fraud detection
  • Confirmation validation

Most exchanges wait for multiple confirmations before crediting deposits.


Example Ethereum Deposit Flow


User Wallet
   ↓
Smart Contract
   ↓
Exchange Deposit Address
   ↓
Blockchain Listener
   ↓
Backend Verification
   ↓
User Balance Updated

Future of Smart Contract Payment Tracking

Blockchain infrastructure is improving rapidly.

Modern indexing systems and AI-assisted blockchain analytics are making smart contract tracking faster and more reliable.

Layer 2 scaling solutions and improved Ethereum tooling are also reducing infrastructure complexity.


FAQ

Why do exchanges reject smart contract deposits?

Some exchanges only monitor direct ETH transfers and cannot reliably detect internal smart contract transactions.

What is the safest way to send ETH to an exchange?

Using a direct wallet-to-wallet transfer is usually the safest option unless the exchange explicitly supports smart contract deposits.

How do exchanges detect deposits?

Exchanges use blockchain monitoring systems, event parsing, and transaction indexing to identify incoming funds.


Final Thoughts

Ethereum smart contracts enable powerful blockchain applications, but they also increase payment tracking complexity.

Modern crypto platforms solve these problems using event parsing, blockchain indexing, and real-time monitoring infrastructure.

As blockchain ecosystems continue evolving, smart contract deposit handling will become faster, more scalable, and more reliable.

CodeChain Dev — Build Modern Products. Solve Real Problems.

Tuesday, October 13, 2020

Top Java Interview Questions and Answers for Developers in 2026

Java is still one of the most important programming languages for backend development, Android development, enterprise applications, fintech systems, and large-scale software platforms.

If you are preparing for a Java developer interview, you should understand the concepts clearly instead of memorizing definitions.

This guide covers important Java interview questions with simple explanations and practical examples.


1. What is Java?

Java is a high-level, object-oriented, platform-independent programming language. It follows the principle of Write Once, Run Anywhere because Java code is compiled into bytecode that runs on the JVM.


2. What is JVM, JRE, and JDK?

Term Meaning
JVM Java Virtual Machine runs Java bytecode.
JRE Java Runtime Environment contains JVM and libraries required to run Java programs.
JDK Java Development Kit contains JRE plus development tools like compiler.

3. What is the difference between Class and Object?

A class is a blueprint. An object is an instance of that class.


class User {
  String name;
}

User user = new User();
user.name = "Salil";

4. What are the main OOP concepts in Java?

  • Encapsulation
  • Inheritance
  • Polymorphism
  • Abstraction

5. What is method overloading?

Method overloading means having multiple methods with the same name but different parameters.


class Calculator {
  int add(int a, int b) {
    return a + b;
  }

  double add(double a, double b) {
    return a + b;
  }
}

6. What is method overriding?

Method overriding happens when a child class provides its own implementation of a method already defined in the parent class.


class Animal {
  void sound() {
    System.out.println("Animal sound");
  }
}

class Dog extends Animal {
  @Override
  void sound() {
    System.out.println("Dog barks");
  }
}

7. What is the difference between overloading and overriding?

Overloading Overriding
Same method name with different parameters Same method name and parameters in child class
Compile-time polymorphism Runtime polymorphism
Can happen in same class Requires inheritance

8. What is inheritance?

Inheritance allows one class to acquire properties and methods of another class.


class Vehicle {
  void start() {
    System.out.println("Vehicle started");
  }
}

class Car extends Vehicle {
}

9. How can we stop inheritance in Java?

Use the final keyword with a class.


final class PaymentService {
}

10. What is encapsulation?

Encapsulation means wrapping data and methods together and restricting direct access using private variables and public methods.


class Account {
  private double balance;

  public double getBalance() {
    return balance;
  }
}

11. What is abstraction?

Abstraction hides implementation details and shows only essential behavior.


abstract class Shape {
  abstract void draw();
}

12. What is the difference between abstract class and interface?

Abstract Class Interface
Can have abstract and non-abstract methods Mainly defines contracts
Supports constructors Does not support constructors
Used for shared base behavior Used for capability-based design

13. What is the difference between == and equals()?

== compares object references. equals() compares object content when properly overridden.


String s1 = "Java";
String s2 = new String("Java");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true

14. What is String Pool?

String Pool is a special memory area where Java stores string literals to optimize memory usage.


String a = "Java";
String b = "Java";

System.out.println(a == b); // true

15. Difference between String, StringBuilder and StringBuffer

Type Use
String Immutable text
StringBuilder Mutable and faster, not thread-safe
StringBuffer Mutable and thread-safe

16. What is exception handling?

Exception handling allows Java programs to handle runtime errors gracefully using try, catch, finally, throw, and throws.


try {
  int result = 10 / 0;
} catch (ArithmeticException e) {
  System.out.println("Cannot divide by zero");
}

17. Checked vs Unchecked Exceptions

Checked Exception Unchecked Exception
Checked at compile time Occurs at runtime
Example: IOException Example: NullPointerException

18. What is HashMap?

HashMap stores data in key-value pairs. It allows fast lookup using hashing.


Map<String, Integer> map = new HashMap<>();
map.put("Java", 1);
System.out.println(map.get("Java"));

19. Difference between ArrayList and LinkedList

ArrayList LinkedList
Fast for searching Fast for insertion/deletion
Uses dynamic array Uses linked nodes

20. What is multithreading?

Multithreading allows multiple tasks to run concurrently in a Java program.


class MyThread extends Thread {
  public void run() {
    System.out.println("Thread running");
  }
}

21. What is synchronization?

Synchronization prevents multiple threads from accessing shared resources at the same time.


synchronized void updateBalance() {
  // critical section
}

22. What is garbage collection?

Garbage collection automatically removes unused objects from memory.

This helps prevent memory leaks and improves memory management.


23. What are Java 8 features?

  • Lambda expressions
  • Stream API
  • Functional interfaces
  • Default methods
  • Optional class

24. What is Stream API?

Stream API is used to process collections in a functional style.


List<String> names = Arrays.asList("Java", "Kotlin", "Android");

names.stream()
     .filter(name -> name.startsWith("J"))
     .forEach(System.out::println);

25. What is an immutable class?

An immutable class cannot be changed after object creation.


final class Employee {
  private final String name;

  Employee(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}

Java Interview Preparation Tips

  • Focus on OOP concepts first
  • Practice collections deeply
  • Understand JVM basics
  • Write small code examples
  • Revise exception handling
  • Practice multithreading questions
  • Prepare Java 8 features

FAQ

Is Java still useful in 2026?

Yes. Java is widely used in enterprise software, Android projects, fintech systems, backend services, and large-scale applications.

Which Java topic is most important for interviews?

OOP, collections, exception handling, multithreading, JVM, and Java 8 features are very important.

Is Java required for Android development?

Kotlin is now preferred for Android, but Java is still useful because many existing Android projects are written in Java.


About the Author

Salil Jha is a Full Stack and Mobile Developer with experience in Android, React Native, blockchain applications, crypto exchange platforms, SaaS products, and scalable backend systems.


Final Thoughts

Java interview preparation becomes easier when you understand concepts with practical examples.

Do not only memorize answers. Practice small programs, understand real use cases, and revise core Java fundamentals regularly.

CodeChain Dev — Build Modern Products. Solve Real Problems.

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.