Firebase is a Backend-as-a-Service (BaaS) by Google. It provides a cloud-hosted infrastructure for developers to build apps without managing servers. It includes tools for authentication, databases, file storage, and hosting.
- Realtime Database: An older, legacy NoSQL database that stores data as one giant JSON Tree. It is optimized for low-latency synchronization (e.g., chat apps).
- Cloud Firestore: A newer NoSQL database that stores data in Documents and Collections. It is better for complex queries, high scalability, and structured data.
Used to register a new user using email and password.
mAuth.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
// User created and logged in automatically
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Signup failed
}
});
mAuth.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener(authResult -> {
// User created successfully
})
.addOnFailureListener(e -> {
// Signup failed
});Used to authenticate an existing user.
mAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
// Login successful
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Login failed
}
});Sends an automated email to the user to reset their password via a secure link.
mAuth.sendPasswordResetEmail(email)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// Email sent
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Error sending email
}
});Only clears the local session on the device. The account remains in Firebase.
mAuth.signOut();Permanently deletes the currently logged-in user's record from Firebase.
mAuth.getCurrentUser().delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// Account deleted
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Error deleting account
}
});public class MainActivity extends AppCompatActivity {
EditText etEmail, etPass;
Button btnLogin, btnSignup, btnReset;
FirebaseAuth mAuth;
FirebaseUser user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAuth = FirebaseAuth.getInstance();
user = mAuth.getCurrentUser();
// Session check: If user exists, go to Profile
if (user != null) {
startActivity(new Intent(this, ProfileActivity.class));
finish();
}
btnLogin.setOnClickListener(v -> {
mAuth.signInWithEmailAndPassword(etEmail.getText().toString(), etPass.getText().toString())
.addOnSuccessListener(authResult -> {
startActivity(new Intent(MainActivity.this, ProfileActivity.class));
})
.addOnFailureListener(e -> Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show());
});
btnSignup.setOnClickListener(v -> {
mAuth.createUserWithEmailAndPassword(etEmail.getText().toString(), etPass.getText().toString())
.addOnSuccessListener(authResult -> {
startActivity(new Intent(MainActivity.this, ProfileActivity.class));
})
.addOnFailureListener(e -> Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show());
});
btnReset.setOnClickListener(v -> {
mAuth.sendPasswordResetEmail(etEmail.getText().toString())
.addOnSuccessListener(unused -> Toast.makeText(this, "Check Email", Toast.LENGTH_SHORT).show());
});
}
}public class ProfileActivity extends AppCompatActivity {
Button btnLogout, btnDelete;
FirebaseAuth mAuth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
mAuth = FirebaseAuth.getInstance();
btnLogout.setOnClickListener(v -> {
mAuth.signOut();
startActivity(new Intent(this, MainActivity.class));
finish();
});
btnDelete.setOnClickListener(v -> {
mAuth.getCurrentUser().delete().addOnSuccessListener(unused -> {
startActivity(new Intent(this, MainActivity.class));
finish();
});
});
}
}In professional development, logic is separated into a "Helper" or "Repository" class to keep activities clean.
public class AuthRepository {
private FirebaseAuth mAuth;
public AuthRepository() {
this.mAuth = FirebaseAuth.getInstance();
}
public void login(String email, String pass, OnSuccessListener<AuthResult> success, OnFailureListener failure) {
mAuth.signInWithEmailAndPassword(email, pass).addOnSuccessListener(success).addOnFailureListener(failure);
}
public void signup(String email, String pass, OnSuccessListener<AuthResult> success, OnFailureListener failure) {
mAuth.createUserWithEmailAndPassword(email, pass).addOnSuccessListener(success).addOnFailureListener(failure);
}
public void resetPassword(String email, OnSuccessListener<Void> success, OnFailureListener failure) {
mAuth.sendPasswordResetEmail(email).addOnSuccessListener(success).addOnFailureListener(failure);
}
public void logout() {
mAuth.signOut();
}
public void deleteAccount(OnSuccessListener<Void> success, OnFailureListener failure) {
if (mAuth.getCurrentUser() != null) {
mAuth.getCurrentUser().delete().addOnSuccessListener(success).addOnFailureListener(failure);
}
}
}- Initialize the repo:
AuthRepository repo = new AuthRepository(); - Call the function and pass the listeners:
repo.login(email, pass,
authResult -> { /* move to dashboard */ },
e -> { /* show error */ }
);In Firebase, every data point needs a "Path".
setValue(): You define the path (Key).
- Usage: When you have a unique ID (like a Student Roll Number or User UID).
- Risk: If the ID exists, it overwrites the data.
push(): Firebase defines the path.
- Usage: When you are adding items to a list (like Chat messages or multiple Courses) and don't care about the specific ID.
- Mechanism: It generates a unique, timestamp-based ID (e.g.,
-Nxyz123...).
When you write:
String key = databaseRef.push().getKey();- Does it hit the server? No.
- How it works: This generation happens locally on your phone instantly. It is mathematically guaranteed to be unique. This allows your app to "create" data even while offline, syncing it later.
Firebase is not a Relational Database (SQL). You do not have "Foreign Keys" or "Joins".
- Scenario: Linking a Teacher to a Course.
- Strategy: Store the ID of the related entity.
- Bad: Storing the entire Teacher object inside the Course node (Data duplication).
- Good: Storing
teacherIdinside the Course node. - Fetching: When you load the Course, you read the
teacherId, then make a second quick call to the "Teachers" node to get that teacher's name.
All examples assume ref points to FirebaseDatabase.getInstance().getReference("Courses").
A. Adding a Whole Object (e.g., New Course)
String id = ref.push().getKey(); // 1. Generate ID locally
Course newCourse = new Course(id, "Android Dev", "CS-502");
ref.child(id).setValue(newCourse) // 2. Send to Cloud
.addOnSuccessListener(unused -> Log.d("FB", "Success"))
.addOnFailureListener(e -> Log.e("FB", "Failed: " + e.getMessage()));B. Adding a Single Field (e.g., Adding 'isActive' flag to existing course)
ref.child(courseId).child("isActive").setValue(true);A. Realtime Stream (ValueEventListener) Used for keeping UI in sync. Triggers every time data changes.
ref.child(courseId).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
Course c = snapshot.getValue(Course.class);
// Update UI
}
}
@Override
public void onCancelled(DatabaseError error) { }
});B. One-Time Fetch (get) Used for checking a value once (e.g., during login or calculation).
ref.child(courseId).get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Course c = task.getResult().getValue(Course.class);
}
});A. Full Overwrite (setValue)
Warning: This replaces the entire node. If you setValue on a course but forget to include the courseCode, the code will be deleted.
B. Specific Field Update (updateChildren) - The Professional Way
Merges new data with existing data.
HashMap<String, Object> updates = new HashMap<>();
updates.put("courseName", "Advanced Android"); // Change Name
updates.put("creditHours", 4); // Change Credits
ref.child(courseId).updateChildren(updates)
.addOnSuccessListener(unused -> Log.d("FB", "Updated"))
.addOnFailureListener(e -> Log.e("FB", "Error: " + e.getMessage()));A. Delete Whole Object
ref.child(courseId).removeValue()
.addOnSuccessListener(unused -> Log.d("FB", "Deleted"));B. Delete Single Field Setting a value to null deletes it.
ref.child(courseId).child("courseName").removeValue();
ref.child(courseId).child("courseName").setValue(null);- Lifecycle Aware: It stops listening when the activity stops (saving battery/data).
- Real-Time Sync: It handles the
ChildEventListenerlogic internally. If User A adds a course, User B sees it appear instantly without "Pull to Refresh." - Code Reduction: Removes the need for a local
ArrayListand manualnotifyDataSetChanged()calls.
- Query: Defines what data to fetch (e.g.,
ref.child("Courses")orref.orderByChild("name")). - FirebaseRecyclerOptions: The configuration object that binds the Query to your Model Class (
Course.class).
Scenario: A "Course Manager" app where you can view lists, add new courses, delete them, and edit their names.
public class Course {
private String id;
private String name;
private String code;
// 1. Empty Constructor (MANDATORY for Firebase)
public Course() {}
// 2. Main Constructor
public Course(String id, String name, String code) {
this.id = id;
this.name = name;
this.code = code;
}
// 3. Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
}<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:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"/>
<TextView android:id="@+id/tvCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#757575"/>
</LinearLayout>
<ImageView android:id="@+id/btnEdit"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@android:drawable/ic_menu_edit"
android:layout_marginEnd="10dp"/>
<ImageView android:id="@+id/btnDelete"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@android:drawable/ic_menu_delete"/>
</LinearLayout>
- NO GETITEMCOUNT() FUNCTION
public class CourseAdapter extends FirebaseRecyclerAdapter<Course, CourseAdapter.CourseViewHolder> {
Context context;
public CourseAdapter(@NonNull FirebaseRecyclerOptions<Course> options, Context context) {
super(options);
this.context = context;
}
@Override
protected void onBindViewHolder(@NonNull CourseViewHolder holder, int position, @NonNull Course model) {
// 1. Set Data
holder.tvName.setText(model.getName());
holder.tvCode.setText(model.getCode());
// 2. Handle Delete
holder.btnDelete.setOnClickListener(v -> {
// "getRef(position)" gives the DatabaseReference to this specific node
getRef(position).removeValue()
.addOnFailureListener(e -> Toast.makeText(context, "Delete Failed", Toast.LENGTH_SHORT).show());
});
// 3. Handle Update (Opens Dialog)
holder.btnEdit.setOnClickListener(v -> showUpdateDialog(model));
}
private void showUpdateDialog(Course model) {
EditText etName = new EditText(context);
etName.setText(model.getName());
new AlertDialog.Builder(context)
.setTitle("Update Course Name")
.setView(etName)
.setPositiveButton("Update", (dialog, which) -> {
String newName = etName.getText().toString();
// Using updateChildren for specific field update
HashMap<String, Object> map = new HashMap<>();
map.put("name", newName);
FirebaseDatabase.getInstance().getReference("Courses")
.child(model.getId())
.updateChildren(map)
.addOnFailureListener(e -> Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show());
})
.setNegativeButton("Cancel", null)
.show();
}
@NonNull
@Override
public CourseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// Standard Inflation
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_course, parent, false);
return new CourseViewHolder(view);
}
class CourseViewHolder extends RecyclerView.ViewHolder {
TextView tvName, tvCode;
ImageView btnDelete, btnEdit;
public CourseViewHolder(@NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvCode = itemView.findViewById(R.id.tvCode);
btnDelete = itemView.findViewById(R.id.btnDelete);
btnEdit = itemView.findViewById(R.id.btnEdit);
}
}
}public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
CourseAdapter adapter;
DatabaseReference baseRef;
FloatingActionButton fabAdd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Initialize Firebase
baseRef = FirebaseDatabase.getInstance().getReference("Courses");
// 2. Setup UI
recyclerView = findViewById(R.id.recyclerView);
fabAdd = findViewById(R.id.fabAdd);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 3. Configure Adapter Options
FirebaseRecyclerOptions<Course> options =
new FirebaseRecyclerOptions.Builder<Course>()
.setQuery(baseRef, Course.class)
.build();
adapter = new CourseAdapter(options, this);
recyclerView.setAdapter(adapter);
// 4. Add New Course Logic
fabAdd.setOnClickListener(v -> showAddDialog());
}
private void showAddDialog() {
// Inflating with NULL because it's a dialog (No parent yet)
View view = LayoutInflater.from(this).inflate(R.layout.dialog_add_course, null);
EditText etName = view.findViewById(R.id.etName);
EditText etCode = view.findViewById(R.id.etCode);
new AlertDialog.Builder(this)
.setTitle("Add New Course")
.setView(view)
.setPositiveButton("Save", (dialog, which) -> {
String name = etName.getText().toString();
String code = etCode.getText().toString();
// PUSH operation
String id = baseRef.push().getKey();
Course newCourse = new Course(id, name, code);
baseRef.child(id).setValue(newCourse)
.addOnSuccessListener(unused -> Toast.makeText(this, "Added", Toast.LENGTH_SHORT).show())
.addOnFailureListener(e -> Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show());
})
.setNegativeButton("Cancel", null)
.show();
}
// MANDATORY: Adapter Lifecycle Methods
@Override
protected void onStart() {
super.onStart();
adapter.startListening(); // Begin sync
}
@Override
protected void onStop() {
super.onStop();
adapter.stopListening(); // Stop sync to save battery
}
}