Notifications & Channels (Java)

Notifications let RecipeBox reach a user even when the app isn't open — a reminder to try a saved recipe tonight, or a heads-up that a background sync is running. Starting a new section on user-facing integrations, this lesson covers the modern, channel-based notification system every Android app targeting recent API levels must use.

Notification Channels Are Required (API 26+)

Since Android 8.0 (API level 26), every notification must be posted to a Notification Channel. A channel groups related notifications under one label the user can control independently — muting "Recipe Reminders" without silencing a "Sync Status" channel from the same app, for example. You create channels once, typically in your Application subclass, and Android ignores the call entirely on older OS versions where channels don't exist.

// RecipeBoxApp.java
public class RecipeBoxApp extends Application {

    public static final String CHANNEL_REMINDERS = "recipe_reminders";

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannels();
    }

    private void createNotificationChannels() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_REMINDERS,
                    "Recipe Reminders",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            channel.setDescription("Reminders to try a saved recipe");

            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }
}
Explanation:
  • The channel ID ("recipe_reminders") is a stable string you reuse whenever you post a notification to this channel.
  • IMPORTANCE_DEFAULT controls the default sound/visibility behavior; users can still override it in system settings.
  • createNotificationChannel() is safe to call repeatedly — creating a channel that already exists with the same ID is a no-op.

Building a Notification with NotificationCompat.Builder

With a channel in place, posting an actual notification uses NotificationCompat.Builder, which works consistently across old and new API levels. This example reminds the user to try tonight's suggested recipe, and tapping it opens RecipeBox directly to that recipe's detail screen via a PendingIntent:

// Showing a reminder for a Recipe the user saved
public void showRecipeReminder(Context context, Recipe recipe) {
    Intent intent = new Intent(context, RecipeDetailActivity.class);
    intent.putExtra("recipe_id", recipe.getId());
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

    PendingIntent pendingIntent = PendingIntent.getActivity(
            context, recipe.getId(), intent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );

    NotificationCompat.Builder builder = new NotificationCompat.Builder(context, RecipeBoxApp.CHANNEL_REMINDERS)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle("Time to cook!")
            .setContentText("Try " + recipe.getTitle() + " tonight")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true);

    NotificationManagerCompat.from(context).notify(recipe.getId(), builder.build());
}
Explanation:
  • setContentIntent() wires the tap action; setAutoCancel(true) dismisses the notification once tapped.
  • The notification ID passed to notify() — here, the recipe's own id — lets you later update or cancel that exact notification.
  • FLAG_IMMUTABLE is required on API 31+ for PendingIntents that don't need to be modified by the receiving component.

The POST_NOTIFICATIONS Runtime Permission (API 33+)

Starting with Android 13 (API level 33), posting any notification requires the POST_NOTIFICATIONS runtime permission, requested the same way as camera or location access described in Runtime Permissions. Without it, calls to notify() silently do nothing on API 33+ devices.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
            != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE_NOTIFICATIONS);
    }
}
Request this permission at a sensible moment — right before RecipeBox would post its first reminder, not immediately on app launch — so the user sees the request in context.

Expandable Styles

A single-line notification truncates longer text. Attaching a NotificationCompat.BigTextStyle lets the user expand it (by pulling down the notification shade) to read the full ingredient list or instructions:

builder.setStyle(new NotificationCompat.BigTextStyle()
        .bigText("Ingredients: " + String.join(", ", recipe.getIngredients())));
Other built-in styles include BigPictureStyle (showing a recipe's photoUri expanded) and InboxStyle (stacking several short lines, useful for "3 recipes expiring soon" summaries).

Foreground Service Notifications Are Persistent

The Services & Foreground Service lesson's sync service must show a notification the entire time it runs — this is what makes it a foreground service rather than a background one the system can kill freely. That notification behaves differently from the reminder above: it cannot be swiped away while the service is active, since it exists specifically to tell the user work is in progress.

// Inside RecipeSyncService, shown while syncing runs
Notification syncNotification = new NotificationCompat.Builder(this, CHANNEL_SYNC)
        .setSmallIcon(R.drawable.ic_sync)
        .setContentTitle("Syncing RecipeBox")
        .setContentText("Updating your saved recipes...")
        .setOngoing(true)
        .build();

startForeground(NOTIFICATION_ID_SYNC, syncNotification);
setOngoing(true) marks it non-dismissible by swipe; calling stopForeground() when the sync finishes removes it (or demotes it back to a normal, dismissible notification).
Notifications bring users back into RecipeBox — the next lesson, App Shortcuts & Widgets, adds quick entry points that live right on the home screen and launcher.