Ever wondered how those apps on your phone actually get made? Today, I’m going to show you exactly how to build a real Android app from start to finish. No fluff, no shortcuts – just practical, working code.
What We’re Building Today
We’re going to create a fully functional To-Do List app called “TaskMaster”. By the end of this tutorial, you’ll have an app that:
- Lets users add new tasks
- Displays all tasks in a scrollable list
- Allows users to mark tasks as complete
- Deletes tasks with a long press
- Saves data so tasks persist when you close the app
- Looks professional with Material Design
Why a To-Do List app? Because it teaches you the fundamentals every Android developer needs: layouts, user input, lists, data persistence, and event handling. Master this, and you can build almost anything.
Time Required: 45-60 minutes (including setup)
Prerequisites:
- A computer (Windows, Mac, or Linux)
- Basic Java knowledge (variables, classes, methods)
- Patience and coffee ☕
Part 1: Setting Up Your Development Environment
Before we write any code, we need the right tools. Think of this like setting up a workshop before building furniture.
Step 1.1: Download and Install Android Studio
Android Studio is Google’s official tool for building Android apps. It’s free and available for all platforms.
- Go to https://developer.android.com/studio
- Click “Download Android Studio”
- Accept the terms and download (around 1GB)
- Install it like any other program
- Windows: Run the .exe file
- Mac: Drag to Applications folder
- Linux: Extract and run
studio.sh
First launch will take 5-10 minutes as it downloads additional components. Let it finish. Grab that coffee now.
Step 1.2: Configure Android Studio
When Android Studio opens for the first time:
- Choose “Standard” installation (recommended)
- Select your preferred theme (I like Darcula, but Light works too)
- Click “Finish” and let it download SDK components
- Wait for “Downloading Components” to complete (10-15 minutes on first install)
Pro tip: If you see any errors about virtualization (Intel HAXM), don’t panic. We’ll test on a real device instead of an emulator.
Part 2: Creating Your First Android Project
Alright, tools are ready. Time to create something!
Step 2.1: Start a New Project
- Click “New Project” on the welcome screen
- Select “Empty Activity” (this gives us a clean slate)
- Click “Next”
Step 2.2: Configure Your Project
Fill in these details:
- Name: TaskMaster
- Package name: com.yourname.taskmaster (use your actual name, lowercase)
- Save location: Choose where you want to save your project
- Language: Java (not Kotlin)
- Minimum SDK: API 21: Android 5.0 (Lollipop)
- This covers 98%+ of Android devices
Click “Finish”.
Android Studio will now build your project. You’ll see “Gradle Build Running” at the bottom. This takes 2-5 minutes the first time. Go stretch your legs.
Step 2.3: Understanding the Project Structure
Once Gradle finishes, you’ll see a bunch of folders. Here’s what matters:
app/
├── manifests/
│ └── AndroidManifest.xml (App configuration)
├── java/
│ └── com.yourname.taskmaster/
│ └── MainActivity.java (Your main code)
└── res/
├── layout/
│ └── activity_main.xml (Your UI design)
├── values/
│ ├── strings.xml (Text strings)
│ └── colors.xml (Color definitions)
└── drawable/ (Images and icons)
Think of it like this:
manifests/= Birth certificate of your appjava/= The brain (logic)res/layout/= The face (UI)res/values/= The vocabulary (text and colors)
Part 3: Designing the User Interface
Time to make our app look good. We’ll use XML to design the interface.
Step 3.1: Open the Layout File
- In the left panel (Project view), navigate to:
app → res → layout → activity_main.xml - You’ll see two tabs at the top: “Code” and “Design”
- Click “Code” (we’ll write XML directly – it’s easier to understand)
Step 3.2: Create the Main Layout
Replace everything in activity_main.xml with this code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:background="#F5F5F5"
tools:context=".MainActivity">
<!-- Title at the top -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TaskMaster"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#1976D2"
android:gravity="center"
android:paddingTop="16dp"
android:paddingBottom="24dp" />
<!-- Input area for new tasks -->
<LinearLayout
android:id="@+id/inputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tvTitle"
android:orientation="horizontal"
android:background="@android:color/white"
android:elevation="4dp"
android:padding="12dp">
<EditText
android:id="@+id/etNewTask"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Enter a new task..."
android:textSize="16sp"
android:background="@null"
android:padding="8dp" />
<Button
android:id="@+id/btnAddTask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add"
android:textColor="@android:color/white"
android:backgroundTint="#1976D2"
android:paddingLeft="24dp"
android:paddingRight="24dp" />
</LinearLayout>
<!-- List of tasks -->
<ListView
android:id="@+id/lvTasks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/inputLayout"
android:layout_marginTop="16dp"
android:divider="#E0E0E0"
android:dividerHeight="1dp"
android:background="@android:color/white"
android:elevation="4dp" />
<!-- Empty state message -->
<TextView
android:id="@+id/tvEmptyState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="No tasks yet!\nTap 'Add' to create your first task"
android:textSize="18sp"
android:textColor="#9E9E9E"
android:gravity="center"
android:visibility="gone" />
</RelativeLayout>
What’s happening here?
RelativeLayout: A flexible container that positions elements relative to each otherTextView: Displays text (our title and empty state message)EditText: Input field where users type their tasksButton: The “Add” button to create tasksListView: Displays our list of tasks (we’ll populate this with Java code)
Color codes:
#1976D2= Material Blue (professional look)#F5F5F5= Light gray background#E0E0E0= Divider lines between tasks
Step 3.3: Create Custom List Item Layout
We need to define how each task looks in the list. Let’s create a new layout file:
- Right-click on
res → layout - Select New → Layout Resource File
- Name it:
task_item.xml - Click OK
Now paste this code into task_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="?attr/selectableItemBackground">
<CheckBox
android:id="@+id/cbTask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:buttonTint="#1976D2" />
<TextView
android:id="@+id/tvTaskName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="12dp"
android:textSize="16sp"
android:textColor="#212121"
android:layout_gravity="center_vertical" />
</LinearLayout>
What this does:
- Each task item has a checkbox and text
- Users can check off completed tasks
- The background has a ripple effect when tapped (Material Design)
Part 4: Writing the Java Code (The Brain)
Now comes the fun part – making everything actually work!
Step 4.1: Create the Task Class
First, we need a class to represent a task. This is basic object-oriented programming.
- Right-click on your package (com.yourname.taskmaster)
- Select New → Java Class
- Name it:
Task - Click OK
Add this code to Task.java:
package com.yourname.taskmaster;
public class Task {
private String taskName;
private boolean isCompleted;
// Constructor
public Task(String taskName, boolean isCompleted) {
this.taskName = taskName;
this.isCompleted = isCompleted;
}
// Getters and setters
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public boolean isCompleted() {
return isCompleted;
}
public void setCompleted(boolean completed) {
isCompleted = completed;
}
// Convert task to string for storage
@Override
public String toString() {
return taskName + "|" + isCompleted;
}
// Create task from stored string
public static Task fromString(String taskString) {
String[] parts = taskString.split("\\|");
return new Task(parts[0], Boolean.parseBoolean(parts[1]));
}
}
Why we need this:
- Organizes our data (every task has a name and completion status)
- Makes it easy to convert tasks to/from strings for storage
- Follows good programming practices (encapsulation)
Step 4.2: Create Custom Adapter
The adapter connects our data (tasks) to the ListView (visual list). It’s like a translator.
- Create new Java class:
TaskAdapter - Add this code:
package com.yourname.taskmaster;
import android.content.Context;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.TextView;
import java.util.ArrayList;
public class TaskAdapter extends ArrayAdapter<Task> {
private Context context;
private ArrayList<Task> tasks;
public TaskAdapter(Context context, ArrayList<Task> tasks) {
super(context, 0, tasks);
this.context = context;
this.tasks = tasks;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the task for this position
Task task = getItem(position);
// Reuse view if possible (performance optimization)
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.task_item, parent, false);
}
// Find the views in task_item.xml
CheckBox cbTask = convertView.findViewById(R.id.cbTask);
TextView tvTaskName = convertView.findViewById(R.id.tvTaskName);
// Set the task name
tvTaskName.setText(task.getTaskName());
// Set checkbox state
cbTask.setChecked(task.isCompleted());
// Add strikethrough if task is completed
if (task.isCompleted()) {
tvTaskName.setPaintFlags(tvTaskName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
tvTaskName.setTextColor(context.getResources().getColor(android.R.color.darker_gray));
} else {
tvTaskName.setPaintFlags(tvTaskName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
tvTaskName.setTextColor(context.getResources().getColor(android.R.color.black));
}
// Handle checkbox clicks
cbTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
task.setCompleted(cbTask.isChecked());
notifyDataSetChanged(); // Refresh the list
}
});
return convertView;
}
}
What this adapter does:
- Takes our list of tasks and displays each one
- Handles the checkbox toggle
- Adds strikethrough text for completed tasks
- Reuses views for better performance (important for long lists)
Step 4.3: Write the Main Activity Code
Now let’s bring everything together in MainActivity.java. This is the heart of the app.
Open MainActivity.java and replace EVERYTHING with this:
package com.yourname.taskmaster;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
// UI Elements
private EditText etNewTask;
private Button btnAddTask;
private ListView lvTasks;
private TextView tvEmptyState;
// Data
private ArrayList<Task> taskList;
private TaskAdapter taskAdapter;
// For saving data
private SharedPreferences sharedPreferences;
private static final String PREFS_NAME = "TaskMasterPrefs";
private static final String TASKS_KEY = "tasks";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize views
etNewTask = findViewById(R.id.etNewTask);
btnAddTask = findViewById(R.id.btnAddTask);
lvTasks = findViewById(R.id.lvTasks);
tvEmptyState = findViewById(R.id.tvEmptyState);
// Initialize data
taskList = new ArrayList<>();
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
// Load saved tasks
loadTasks();
// Set up adapter
taskAdapter = new TaskAdapter(this, taskList);
lvTasks.setAdapter(taskAdapter);
// Update empty state visibility
updateEmptyState();
// Add task button click
btnAddTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addTask();
}
});
// Long press to delete task
lvTasks.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
showDeleteConfirmation(position);
return true;
}
});
}
/**
* Adds a new task to the list
*/
private void addTask() {
String taskName = etNewTask.getText().toString().trim();
// Validate input
if (taskName.isEmpty()) {
Toast.makeText(this, "Please enter a task", Toast.LENGTH_SHORT).show();
return;
}
// Create new task
Task newTask = new Task(taskName, false);
taskList.add(newTask);
// Update UI
taskAdapter.notifyDataSetChanged();
etNewTask.setText(""); // Clear input field
updateEmptyState();
// Save tasks
saveTasks();
// Show confirmation
Toast.makeText(this, "Task added!", Toast.LENGTH_SHORT).show();
}
/**
* Shows confirmation dialog before deleting a task
*/
private void showDeleteConfirmation(final int position) {
Task task = taskList.get(position);
new AlertDialog.Builder(this)
.setTitle("Delete Task")
.setMessage("Are you sure you want to delete \"" + task.getTaskName() + "\"?")
.setPositiveButton("Delete", (dialog, which) -> {
// Remove task
taskList.remove(position);
taskAdapter.notifyDataSetChanged();
updateEmptyState();
saveTasks();
Toast.makeText(MainActivity.this, "Task deleted", Toast.LENGTH_SHORT).show();
})
.setNegativeButton("Cancel", null)
.show();
}
/**
* Shows/hides empty state message
*/
private void updateEmptyState() {
if (taskList.isEmpty()) {
tvEmptyState.setVisibility(View.VISIBLE);
lvTasks.setVisibility(View.GONE);
} else {
tvEmptyState.setVisibility(View.GONE);
lvTasks.setVisibility(View.VISIBLE);
}
}
/**
* Saves tasks to SharedPreferences
*/
private void saveTasks() {
Set<String> taskStrings = new HashSet<>();
for (Task task : taskList) {
taskStrings.add(task.toString());
}
sharedPreferences.edit().putStringSet(TASKS_KEY, taskStrings).apply();
}
/**
* Loads tasks from SharedPreferences
*/
private void loadTasks() {
Set<String> taskStrings = sharedPreferences.getStringSet(TASKS_KEY, new HashSet<>());
for (String taskString : taskStrings) {
try {
taskList.add(Task.fromString(taskString));
} catch (Exception e) {
// Skip corrupted task data
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
super.onPause();
// Save tasks when app goes to background
saveTasks();
}
}
Let me explain the key parts:
- onCreate(): Runs when the app starts
- Connects Java code to XML views using
findViewById() - Loads saved tasks from storage
- Sets up the adapter to display tasks
- Configures button clicks
- Connects Java code to XML views using
- addTask(): Handles adding new tasks
- Validates that user entered something
- Creates new Task object
- Updates the list
- Saves to storage
- Shows confirmation message
- showDeleteConfirmation(): Handles task deletion
- Shows a dialog to confirm deletion
- Removes task if confirmed
- Saves changes
- saveTasks() / loadTasks(): Data persistence
- Uses SharedPreferences (lightweight key-value storage)
- Converts tasks to strings and back
- Ensures tasks survive app restarts
- updateEmptyState(): UI polish
- Shows helpful message when list is empty
- Hides it when tasks exist
Part 5: Testing Your App
Time for the moment of truth! Let’s run this thing.
Option A: Test on Real Device (Recommended)
Why real device? It’s faster, more reliable, and shows you exactly how users will experience your app.
Steps:
- Enable Developer Mode on your Android phone:
- Go to Settings → About Phone
- Tap “Build Number” 7 times (yes, really)
- You’ll see “You are now a developer!”
- Enable USB Debugging:
- Go to Settings → Developer Options
- Turn on “USB Debugging”
- Accept the security warning
- Connect phone to computer:
- Use a USB cable
- On your phone, tap “Allow USB Debugging” when prompted
- Run the app in Android Studio:
- Click the green “Run” button (or press Shift+F10)
- Select your device from the dropdown
- Click OK
First run takes 1-2 minutes as Gradle builds everything. Subsequent runs are faster.
Testing Checklist
Once your app launches, test these features:
✅ Type a task and tap “Add” – does it appear?
✅ Tap the checkbox – does it strikethrough?
✅ Long press a task – does delete dialog appear?
✅ Delete a task – does it disappear?
✅ Close app and reopen – do tasks persist?
✅ Try adding an empty task – does it show error?
Part 6: Building and Exporting Your App (APK)
Generate Signed APK
- Click Build → Generate Signed Bundle / APK
- Select APK → Click Next
- Click Create new… (for Key store path)
Create Keystore
Fill in these details:
- Key store path: Choose where to save it
- Password: Create a strong password (write it down!)
- Alias: taskmaster-key
- Validity: 25 years (default)
- First and Last Name: Your name
Click OK, then Next, select release, and click Finish.
Your APK will be at: app/release/app-release.apk
Congratulations! This is your finished app.
Troubleshooting Common Issues
“Cannot resolve symbol R”
- Click Build → Clean Project
- Then Build → Rebuild Project
App crashes on launch
- Check Logcat for error messages
- Verify package names match
Tasks don’t save
- Check that
saveTasks()is called inonPause() - Clear app data and test again
Next Steps
Congratulations! You’ve built a real Android app. Here are some enhancements to try:
- Add Task Priority (High, Medium, Low)
- Add Due Dates using DatePicker
- Categories/Tags for organizing tasks
- Dark Mode Support
- Cloud Sync with Firebase
Full Source Code Here!
Final Thoughts
You just built a real Android app from scratch. That’s legitimately impressive.
What you learned:
✅ Android Studio setup
✅ XML layout design
✅ Java programming for Android
✅ Data persistence
✅ User interface patterns
✅ App lifecycle management
✅ Building and exporting APKs
The best way to learn is by building. Take this app and modify it. Break it. Fix it. Add features. Make it yours.
Every expert was once a beginner who didn’t give up.
Keep coding. Keep learning. Keep building.
Did this tutorial help you? Share it with other aspiring Android developers!

