Wednesday, August 26, 2020

How to Build a Countdown Timer in Android Using CountDownTimer

No comments email this post Edit Post

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

No comments email this post Edit Post

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

No comments email this post Edit Post

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.

Tuesday, June 9, 2020

How to Use GSON Library in Android for JSON Parsing

No comments email this post Edit Post

JSON parsing is one of the most important concepts in Android app development.

Most modern Android applications communicate with APIs and servers using JSON data.

Google provides the powerful GSON Library to easily:

  • Convert Java objects into JSON
  • Convert JSON into Java objects
  • Serialize data
  • Deserialize API responses
  • Map JSON keys to Java variables

In this tutorial, we will learn how to use the GSON library in Android Studio with practical examples.


What We Will Learn

  • What is GSON?
  • How to add GSON dependency
  • Convert Java object to JSON
  • Convert JSON to Java object
  • Use @SerializedName annotation
  • Parse JSON efficiently

What is GSON?

GSON is a Java library developed by Google that simplifies:

  • JSON serialization
  • JSON deserialization

GSON automatically maps JSON data to Java classes with minimal code.


Why Use GSON in Android?

GSON is widely used because:

  • Easy to use
  • Fast parsing
  • Lightweight
  • Works well with Retrofit
  • Supports nested JSON
  • Supports custom field names

Step 1 — Add GSON Dependency

Inside:


build.gradle

Add:


implementation 'com.google.code.gson:gson:2.10.1'

Step 2 — Create Employee.java Class

Create:


Employee.java

package com.example.gsonexample;

import com.google.gson.annotations.SerializedName;

public class Employee {

    @SerializedName("first_name")
    private String firstName;

    @SerializedName("age")
    private int age;

    @SerializedName("mail")
    private String email;

    public Employee(
            String firstName,
            int age,
            String email
    ) {

        this.firstName = firstName;
        this.age = age;
        this.email = email;
    }

    public String getFirstName() {
        return firstName;
    }

    public int getAge() {
        return age;
    }

    public String getEmail() {
        return email;
    }
}

What is @SerializedName?

The:


@SerializedName

annotation allows different JSON keys and Java variable names.

Example:


@SerializedName("first_name")
private String firstName;

JSON key:


"first_name"

maps to:


firstName

Step 3 — Convert Java Object to JSON

Inside:


MainActivity.java

package com.example.gsonexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import com.google.gson.Gson;

public class MainActivity
        extends AppCompatActivity {

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        Gson gson = new Gson();

        Employee employee =
                new Employee(
                        "John",
                        30,
                        "john@gmail.com"
                );

        String json =
                gson.toJson(employee);

        Log.d("JSON_OUTPUT", json);
    }
}

Generated JSON Output


{
  "first_name":"John",
  "age":30,
  "mail":"john@gmail.com"
}

Step 4 — Convert JSON to Java Object


package com.example.gsonexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import com.google.gson.Gson;

public class MainActivity
        extends AppCompatActivity {

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        Gson gson = new Gson();

        String json =
                "{\"first_name\":\"John\",\"age\":30,\"mail\":\"john@gmail.com\"}";

        Employee employee =
                gson.fromJson(
                        json,
                        Employee.class
                );

        Log.d(
                "EMPLOYEE_NAME",
                employee.getFirstName()
        );
    }
}

How GSON Parsing Works

The workflow is:

  1. JSON string received from API
  2. GSON reads JSON structure
  3. Maps JSON keys to Java variables
  4. Creates Java object automatically

Example JSON File

employee.json


{
  "age": 30,
  "first_name": "John",
  "mail": "john@gmail.com"
}

Real-World Use Cases

GSON is commonly used in:

  • REST API integration
  • Retrofit networking
  • Firebase APIs
  • Local JSON storage
  • Configuration files
  • Caching systems

Modern Android Recommendation

Most modern Android applications combine:

  • Retrofit
  • OkHttp
  • GSON
  • MVVM Architecture
  • Coroutines

for scalable networking systems.


Common Beginner Mistakes

1. Using Wrong JSON Keys

JSON key names must match:


@SerializedName

2. Forgetting Getters

Always create getters if object data must be accessed later.


3. Invalid JSON Format

Malformed JSON causes parsing exceptions.


GSON vs Moshi vs Kotlin Serialization

Library Advantages
GSON Simple and beginner friendly
Moshi Modern and optimized
Kotlin Serialization Best for Kotlin-first apps

Advanced GSON Features

  • Nested object parsing
  • Array parsing
  • Custom serializers
  • Custom deserializers
  • TypeToken support
  • Null handling

FAQ

Can GSON parse API responses?

Yes. GSON is commonly used with Retrofit for parsing API responses automatically.

Is GSON still widely used?

Yes. Although newer alternatives exist, GSON remains very popular in Android development.

Can GSON parse arrays?

Yes. GSON can parse:

  • Lists
  • Arrays
  • Nested objects
  • Complex JSON structures

Conclusion

GSON simplifies JSON serialization and deserialization in Android applications.

It allows developers to convert Java objects and JSON data easily with minimal boilerplate code.

Modern Android applications frequently use GSON with Retrofit and MVVM architecture for scalable networking 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.

Wednesday, January 22, 2020

How to Pass Data to a Fragment Using Bundle and Factory Method in Android

No comments email this post Edit Post

Passing data between Activities and Fragments is one of the most common tasks in Android development.

However, many beginners incorrectly pass values using custom Fragment constructors, which can cause runtime issues and lifecycle problems.

Modern Android development recommends using:

  • Bundle arguments
  • Factory methods
  • setArguments()

In this tutorial, we will learn:

  • Why overloaded Fragment constructors are dangerous
  • How to pass values using Bundle
  • How to create a factory method
  • Modern Android Fragment best practices

Why Custom Fragment Constructors Are Not Recommended

Android recreates Fragments automatically during:

  • Screen rotation
  • Configuration changes
  • Process recreation
  • Low memory recovery

During recreation, Android uses the default no-argument constructor.

If developers create custom constructors like:


public ExampleFragment(String text) {

}

Android may throw:


InstantiationException

or lifecycle restoration problems.


Correct Way to Pass Data to Fragments

Modern Android recommends:

  1. Create a Bundle
  2. Add values into Bundle
  3. Call setArguments()
  4. Retrieve values using getArguments()

What Is a Factory Method?

A Factory Method is a static method used to safely create Fragment instances with arguments.

Benefits:

  • Cleaner code
  • Better lifecycle handling
  • Avoids constructor issues
  • Improves reusability

What We Will Build

In this Android example:

  • MainActivity creates a Fragment
  • Bundle values are passed safely
  • Fragment retrieves arguments
  • UI displays passed values

Step 1 — Create activity_main.xml

Create:


res/layout/activity_main.xml

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

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

    android:id="@+id/container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"/>

Step 2 — Create MainActivity.java

Create:


MainActivity.java

package com.example.fragmentarguments;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity
        extends AppCompatActivity {

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        ExampleFragment fragment =
                ExampleFragment.newInstance(
                        "Example Text",
                        123
                );

        getSupportFragmentManager()
                .beginTransaction()
                .replace(
                        R.id.container,
                        fragment
                )
                .commit();
    }
}

Step 3 — Create ExampleFragment.java

Create:


ExampleFragment.java

package com.example.fragmentarguments;

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;
import android.widget.TextView;

public class ExampleFragment
        extends Fragment {

    private static final String ARG_TEXT =
            "argText";

    private static final String ARG_NUMBER =
            "argNumber";

    private String text;

    private int number;

    public ExampleFragment() {

        // Required empty constructor
    }

    public static ExampleFragment newInstance(
            String text,
            int number
    ) {

        ExampleFragment fragment =
                new ExampleFragment();

        Bundle args = new Bundle();

        args.putString(
                ARG_TEXT,
                text
        );

        args.putInt(
                ARG_NUMBER,
                number
        );

        fragment.setArguments(args);

        return fragment;
    }

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

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

        TextView textView =
                view.findViewById(
                        R.id.text_view_fragment
                );

        if (getArguments() != null) {

            text =
                    getArguments().getString(
                            ARG_TEXT
                    );

            number =
                    getArguments().getInt(
                            ARG_NUMBER
                    );
        }

        textView.setText(
                text + " " + number
        );

        return view;
    }
}

Step 4 — Create example_fragment.xml

Create:


res/layout/example_fragment.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:orientation="vertical"

    android:gravity="center"

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

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

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Fragment Text"

        android:textSize="22sp"/>

</LinearLayout>

How This Implementation Works

The workflow is:

  1. MainActivity creates Fragment using factory method
  2. Bundle stores arguments safely
  3. Fragment receives arguments using setArguments()
  4. getArguments() retrieves stored values
  5. UI displays received data

Why setArguments() Is Important

Using:


setArguments()

allows Android to automatically restore Fragment state during:

  • Configuration changes
  • Process recreation
  • Navigation restoration

Common Mistakes Developers Make

1. Using Custom Constructors

Custom Fragment constructors can break lifecycle restoration.


2. Forgetting Empty Constructor

Fragments should always have:


public ExampleFragment() {

}

3. Accessing getArguments() Without Null Check

Always check:


if (getArguments() != null)

before reading Bundle values.


Modern Android Recommendations

Modern Android applications often use:

  • Safe Args Navigation Component
  • Shared ViewModel
  • Jetpack Compose navigation
  • StateFlow
  • Parcelable data transfer

Bundle vs Shared ViewModel

Bundle Arguments Shared ViewModel
Good for initial data Good for shared state
Lifecycle restoration friendly Reactive UI updates
Simple implementation Better for complex apps

Modern Navigation Alternative

Modern Android apps commonly use:

  • Navigation Component
  • Safe Args Plugin
  • Compose Navigation

These provide type-safe argument passing with better lifecycle handling.


FAQ

Why should Fragments avoid constructors with parameters?

Android recreates Fragments automatically using the default constructor.

Can Bundle store objects?

Yes. Bundle supports Serializable and Parcelable objects.

What is the modern recommended approach?

Navigation Component with Safe Args is the modern recommended solution for Fragment argument passing.


Conclusion

Passing data to Fragments using Bundle arguments and factory methods is the safest and most lifecycle-aware approach in Android development.

This pattern improves:

  • Lifecycle safety
  • Fragment restoration
  • Code readability
  • Reusability

Modern Android applications should combine Bundle arguments with Navigation Component, Shared ViewModel, and lifecycle-aware architecture for scalable Fragment communication.


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.

Friday, January 17, 2020

How to Send Data Between Two Fragments in Android

No comments email this post Edit Post

Fragments are reusable UI components commonly used in modern Android applications.

Many Android applications require communication between fragments for:

  • Form updates
  • Search filters
  • Chat interfaces
  • Dashboard synchronization
  • Master-detail layouts

Since fragments should remain modular and reusable, direct fragment-to-fragment communication is not recommended.

Instead, communication usually happens through:

  • Activity interfaces
  • Shared ViewModel
  • Fragment Result API

In this tutorial, we will learn how to send data between two fragments using interfaces and the hosting Activity.


What We Will Build

In this Android example:

  • Fragment A sends text to Fragment B
  • Fragment B sends text to Fragment A
  • Activity acts as communication bridge
  • Fragments remain reusable and modular

How Fragment Communication Works

The workflow is:

  1. User enters text in Fragment A
  2. Fragment A sends data to Activity using interface
  3. Activity forwards data to Fragment B
  4. Fragment B updates UI

The same process works in reverse direction.


Step 1 — Create activity_main.xml

Create the main Activity layout:


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

    <FrameLayout
        android:id="@+id/containerA"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <FrameLayout
        android:id="@+id/containerB"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

Step 2 — Create MainActivity.java


package com.example.fragmentcommunication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity
        extends AppCompatActivity
        implements FragmentA.FragmentAListener,
                   FragmentB.FragmentBListener {

    private FragmentA fragmentA;

    private FragmentB fragmentB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        fragmentA = new FragmentA();

        fragmentB = new FragmentB();

        getSupportFragmentManager()
                .beginTransaction()
                .replace(
                        R.id.containerA,
                        fragmentA
                )
                .replace(
                        R.id.containerB,
                        fragmentB
                )
                .commit();
    }

    @Override
    public void onInputASent(CharSequence input) {

        fragmentB.updateEditText(input);
    }

    @Override
    public void onInputBSent(CharSequence input) {

        fragmentA.updateEditText(input);
    }
}

Step 3 — Create FragmentA.java


package com.example.fragmentcommunication;

import android.content.Context;
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;
import android.widget.Button;
import android.widget.EditText;

public class FragmentA extends Fragment {

    private FragmentAListener listener;

    private EditText editText;

    public interface FragmentAListener {

        void onInputASent(CharSequence input);
    }

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

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

        editText =
                view.findViewById(R.id.editText);

        Button button =
                view.findViewById(R.id.buttonOk);

        button.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        listener.onInputASent(
                                editText.getText()
                        );
                    }
                });

        return view;
    }

    public void updateEditText(
            CharSequence newText
    ) {

        editText.setText(newText);
    }

    @Override
    public void onAttach(
            @NonNull Context context
    ) {

        super.onAttach(context);

        if (context instanceof FragmentAListener) {

            listener =
                    (FragmentAListener) context;

        } else {

            throw new RuntimeException(
                    context.toString()
                            + " must implement FragmentAListener"
            );
        }
    }
}

Step 4 — Create FragmentB.java


package com.example.fragmentcommunication;

import android.content.Context;
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;
import android.widget.Button;
import android.widget.EditText;

public class FragmentB extends Fragment {

    private FragmentBListener listener;

    private EditText editText;

    public interface FragmentBListener {

        void onInputBSent(CharSequence input);
    }

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

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

        editText =
                view.findViewById(R.id.editText);

        Button button =
                view.findViewById(R.id.buttonOk);

        button.setOnClickListener(
                new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        listener.onInputBSent(
                                editText.getText()
                        );
                    }
                });

        return view;
    }

    public void updateEditText(
            CharSequence newText
    ) {

        editText.setText(newText);
    }

    @Override
    public void onAttach(
            @NonNull Context context
    ) {

        super.onAttach(context);

        if (context instanceof FragmentBListener) {

            listener =
                    (FragmentBListener) context;

        } else {

            throw new RuntimeException(
                    context.toString()
                            + " must implement FragmentBListener"
            );
        }
    }
}

Step 5 — Create fragment_a.xml


<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"
    android:background="#A5D6A7">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

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

</LinearLayout>

Step 6 — Create fragment_b.xml


<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"
    android:background="#90CAF9">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

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

</LinearLayout>

Why Interfaces Are Used

Interfaces help:

  • Reduce tight coupling
  • Improve modularity
  • Make fragments reusable
  • Separate responsibilities cleanly

Common Mistakes Developers Make

1. Direct Fragment References

Direct fragment communication creates tightly coupled code.


2. Forgetting Interface Implementation

Activity must implement fragment listener interfaces.


3. Updating Views Before Initialization

Always ensure fragment views are initialized before updating UI.


Modern Android Alternatives

Modern Android development usually prefers:

  • Shared ViewModel
  • LiveData
  • StateFlow
  • Fragment Result API
  • Jetpack Compose state management

Shared ViewModel vs Interface Communication

Interface Communication Shared ViewModel
Good for simple communication Better for scalable apps
Activity acts as bridge Lifecycle-aware state sharing
More manual implementation Cleaner architecture

FAQ

Can fragments communicate directly?

Technically yes, but it is not recommended because it increases coupling.

What is the modern recommended approach?

Shared ViewModel with LiveData or StateFlow is the modern recommended solution.

Why use fragments instead of multiple activities?

Fragments provide better UI flexibility and reusable modular components.


Conclusion

Fragment communication is an important part of Android application architecture.

Using interfaces allows fragments to remain modular while communicating safely through the hosting Activity.

Modern Android applications should combine fragment communication with lifecycle-aware architecture patterns such as Shared ViewModel and StateFlow.


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.

Tuesday, January 14, 2020

Android Notification Channels Tutorial — Modern Push Notification System

No comments email this post Edit Post

Notifications are one of the most important features in Android applications.

Modern Android apps use notifications for:

  • Messages and chats
  • Order updates
  • Downloads
  • Media playback
  • Reminders and alerts
  • Background services

Since Android Oreo (API 26), Notification Channels became mandatory for displaying notifications.

In this tutorial, we will learn:

  • What Notification Channels are
  • How to create notification channels
  • How to send notifications
  • Modern notification best practices
  • Notification priorities and importance levels

What Are Notification Channels?

Notification Channels allow Android users to control notification behavior for different categories inside an app.

Examples:

  • Chat messages
  • Promotions
  • Order alerts
  • Download progress

Users can individually control:

  • Sound
  • Vibration
  • Priority
  • Popup behavior
  • Lockscreen visibility

Why Notification Channels Are Important

Android 8.0 (API level 26) requires every notification to belong to a notification channel.

Without channels:

  • Notifications will not appear
  • Users lose notification customization control
  • Apps may behave inconsistently across Android versions

What We Will Build

In this Android example:

  • Create two notification channels
  • Send notifications on different channels
  • Use NotificationCompat
  • Support modern Android versions

Step 1 — Configure AndroidManifest.xml

Inside:


AndroidManifest.xml

Add custom Application class:


<application
    android:name=".App"
    android:allowBackup="true"
    android:theme="@style/Theme.App">

    <activity
        android:name=".MainActivity">

        <intent-filter>

            <action
                android:name=
                "android.intent.action.MAIN" />

            <category
                android:name=
                "android.intent.category.LAUNCHER" />

        </intent-filter>

    </activity>

</application>

Step 2 — Create Notification Channels

Create:


App.java

Add the following code:


package com.example.notifications;

import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;

public class App extends Application {

    public static final String CHANNEL_1_ID =
            "channel1";

    public static final String CHANNEL_2_ID =
            "channel2";

    @Override
    public void onCreate() {

        super.onCreate();

        createNotificationChannels();
    }

    private void createNotificationChannels() {

        if (Build.VERSION.SDK_INT
                >= Build.VERSION_CODES.O) {

            NotificationChannel channel1 =
                    new NotificationChannel(
                            CHANNEL_1_ID,
                            "Important Notifications",
                            NotificationManager
                                    .IMPORTANCE_HIGH
                    );

            channel1.setDescription(
                    "High priority notifications"
            );

            NotificationChannel channel2 =
                    new NotificationChannel(
                            CHANNEL_2_ID,
                            "General Notifications",
                            NotificationManager
                                    .IMPORTANCE_LOW
                    );

            channel2.setDescription(
                    "Low priority notifications"
            );

            NotificationManager manager =
                    getSystemService(
                            NotificationManager.class
                    );

            manager.createNotificationChannel(
                    channel1
            );

            manager.createNotificationChannel(
                    channel2
            );
        }
    }
}

Step 3 — Create activity_main.xml


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

    <EditText
        android:id="@+id/editTextTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Title" />

    <EditText
        android:id="@+id/editTextMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Message" />

    <Button
        android:id="@+id/buttonChannel1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send High Priority" />

    <Button
        android:id="@+id/buttonChannel2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Low Priority" />

</LinearLayout>

Step 4 — Create MainActivity.java


package com.example.notifications;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import android.app.Notification;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity
        extends AppCompatActivity {

    private EditText editTextTitle;

    private EditText editTextMessage;

    private NotificationManagerCompat
            notificationManager;

    @Override
    protected void onCreate(
            Bundle savedInstanceState
    ) {

        super.onCreate(savedInstanceState);

        setContentView(
                R.layout.activity_main
        );

        notificationManager =
                NotificationManagerCompat
                        .from(this);

        editTextTitle =
                findViewById(
                        R.id.editTextTitle
                );

        editTextMessage =
                findViewById(
                        R.id.editTextMessage
                );

        Button buttonChannel1 =
                findViewById(
                        R.id.buttonChannel1
                );

        Button buttonChannel2 =
                findViewById(
                        R.id.buttonChannel2
                );

        buttonChannel1.setOnClickListener(v -> {

            sendNotificationChannel1();
        });

        buttonChannel2.setOnClickListener(v -> {

            sendNotificationChannel2();
        });
    }

    private void sendNotificationChannel1() {

        Notification notification =
                new NotificationCompat.Builder(
                        this,
                        App.CHANNEL_1_ID
                )

                .setSmallIcon(
                        R.drawable.ic_notification
                )

                .setContentTitle(
                        editTextTitle
                                .getText()
                                .toString()
                )

                .setContentText(
                        editTextMessage
                                .getText()
                                .toString()
                )

                .setPriority(
                        NotificationCompat
                                .PRIORITY_HIGH
                )

                .build();

        notificationManager.notify(
                1,
                notification
        );
    }

    private void sendNotificationChannel2() {

        Notification notification =
                new NotificationCompat.Builder(
                        this,
                        App.CHANNEL_2_ID
                )

                .setSmallIcon(
                        R.drawable.ic_notification
                )

                .setContentTitle(
                        editTextTitle
                                .getText()
                                .toString()
                )

                .setContentText(
                        editTextMessage
                                .getText()
                                .toString()
                )

                .setPriority(
                        NotificationCompat
                                .PRIORITY_LOW
                )

                .build();

        notificationManager.notify(
                2,
                notification
        );
    }
}

Understanding Notification Importance Levels

Importance Behavior
IMPORTANCE_HIGH Popup + sound + heads-up
IMPORTANCE_DEFAULT Sound only
IMPORTANCE_LOW Silent notification
IMPORTANCE_MIN Background only

NotificationCompat vs Notification

Modern Android apps should use:


NotificationCompat

because it provides:

  • Backward compatibility
  • Better feature support
  • Consistent behavior across devices

Modern Android Notification Features

Android notifications now support:

  • Action buttons
  • Direct reply
  • Progress bars
  • Media controls
  • MessagingStyle
  • BigPictureStyle
  • Grouped notifications
  • Foreground services

Common Notification Mistakes

1. Forgetting Notification Channels

Notifications will not appear on Android Oreo+ without channels.


2. Using High Priority Everywhere

Overusing high-priority notifications annoys users.


3. Missing Notification Permission

Android 13+ requires runtime notification permission:


POST_NOTIFICATIONS

Android 13 Notification Permission

For Android 13 and above:


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

Runtime permission request is also required.


Best Practices for Notifications

  • Use meaningful notification channels
  • Avoid spam notifications
  • Provide user customization
  • Use proper priorities
  • Group related notifications
  • Use deep links for navigation

FAQ

Why are notification channels required?

Android Oreo introduced channels to give users more control over notification behavior.

Can notification settings be changed later?

Once created, some channel settings become user-controlled and cannot be changed programmatically.

Should every notification have its own channel?

No. Channels should represent categories of notifications.


Conclusion

Notification Channels are a core part of modern Android notification systems.

Proper notification architecture improves:

  • User engagement
  • Notification management
  • Application usability
  • Android compatibility

Modern Android applications should combine NotificationCompat, runtime permissions, proper channel grouping, and user-friendly notification design for scalable notification 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.