Getting a failed to post notification on channel “channel_id_here” error

Tags: , , , ,



I have been struggling for days now to get local notifications to display on an Android device. Notifications simply do not show up and I’m getting a developer warning:

package Failed to post notification on channel "channel_id_here" error.

I went through many tutorials and StackOverflow posts to see if there’s anything I missed, but I simply can’t find it. Can someone please take a look if I did something wrong here or if there’s something I’m missing. I would appreciate some assistance in sorting this out.

My app is targeting API 29 and I used code from this git

Here is the relevant code from my app:

AppCompatPreferenceActivity.java

public abstract class AppCompatPreferenceActivity extends PreferenceActivity {

    private AppCompatDelegate mDelegate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getDelegate().installViewFactory();
        getDelegate().onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        getDelegate().onPostCreate(savedInstanceState);
    }

    public ActionBar getSupportActionBar() {
        return getDelegate().getSupportActionBar();
    }

    public void setSupportActionBar(@Nullable Toolbar toolbar) {
        getDelegate().setSupportActionBar(toolbar);
    }

    @Override
    public MenuInflater getMenuInflater() {
        return getDelegate().getMenuInflater();
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().addContentView(view, params);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        getDelegate().setTitle(title);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        getDelegate().onConfigurationChanged(newConfig);
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }

    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    private AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, null);
        }
        return mDelegate;
    }
}

 

NotificationPublisher.java

public class NotificationPublisher extends BroadcastReceiver {
    public static String NOTIFICATION = "notification";

    @Override
    public void onReceive(Context ctx, Intent intent) {
        NotificationManager notificationManager = (NotificationManager)
                ctx.getSystemService(Context.NOTIFICATION_SERVICE);

        // The notification may come from the received intent (see SettingsActivity for how to build
        // it and add it to the intent). If not then we'll build a notification here.
        Notification notification;
        if (intent.hasExtra(NOTIFICATION)) {
            notification = intent.getParcelableExtra(NOTIFICATION);
        } else {
            notification = buildNotification(ctx);
        }

        // notificationId is a unique int for each notification.
        // TODO Is the current time good enough?
        int notificationId = (int) System.currentTimeMillis();

        notificationManager.notify(notificationId, notification);
    }

    private Notification buildNotification(Context ctx) {
        Intent intent = new Intent(ctx, com.app.myapp.activity.MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, intent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_stat_notification)
                .setContentTitle("Hello I'm a notification!")
                .setContentText("Well look at that, it's content")
                .setContentIntent(pendingIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);

        return builder.build();
    }
}

NotificationSchedulerApplication.java

public class NotificationSchedulerApplication extends Application {

    public static final String CHANNEL_ID = "1998882";

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

    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = getString(R.string.channel_name);
            String description = getString(R.string.channel_description);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            channel.enableLights(true);
            channel.setLightColor(Color.RED);
            channel.enableVibration(true);
            channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            channel.setDescription(description);
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

SettingsActivity.java

public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {

    private static final String ENABLE_NOTIFICATIONS_PREF_KEY = "notifications_enable";
    private static final String NOTIFICATION_TIME_PREF_KEY = "notifications_time";

    // This request code is used for all PendingIntents so that PendingIntents previously submitted
    // to the AlarmManager can be looked up for cancellation or modification.
    private static final int REQUEST_CODE = 0;

    /**
     * A preference value change listener that updates the preference's summary
     * to reflect its new value.
     */
    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object value) {
            if (preference instanceof ListPreference) {
                // For list preferences, look up the correct display value in
                // the preference's 'entries' list.
                ListPreference listPreference = (ListPreference) preference;
                int index = listPreference.findIndexOfValue(value.toString());

                preference.setSummary(
                        index >= 0
                                ? listPreference.getEntries()[index]
                                : null);
            } else if (preference instanceof TimePreference) {
                TimePreference timePreference = (TimePreference) preference;
                preference.setSummary(timePreference.valueToSummary((int) value));
            } else {
                // For all other preferences, set the summary to the value's
                // simple string representation.
                preference.setSummary(value.toString());
            }
            return true;
        }
    };

    /**
     * Helper method to determine if the device has an extra-large screen. For
     * example, 10" tablets are extra-large.
     */
    private static boolean isXLargeTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout
                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }

    /**
     * Binds a preference's summary to its value. More specifically, when the
     * preference's value is changed, its summary (line of text below the
     * preference title) is updated to reflect the value. The summary is also
     * immediately updated upon calling this method. The exact display format is
     * dependent on the type of preference.
     *
     * @see #sBindPreferenceSummaryToValueListener
     */
    private static void bindPreferenceSummaryToValue(Preference preference) {
        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);

        // Trigger the listener immediately with the preference's current value.
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(
                preference.getContext());
        Object value;
        if (preference instanceof TimePreference) {
            value = sharedPrefs.getInt(preference.getKey(), 0);
        } else {
            value = sharedPrefs.getString(preference.getKey(), "");
        }
        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, value);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupActionBar();

        // Listen for changes to any preferences.
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        prefs.registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
        // We only want to take action if these particular preferences change.
        if (!s.equals(ENABLE_NOTIFICATIONS_PREF_KEY) && !s.equals(NOTIFICATION_TIME_PREF_KEY)) {
            return;
        }

        // If the preference was changed to false then we should cancel any pending notifications.
        if (s.equals(ENABLE_NOTIFICATIONS_PREF_KEY) && !sharedPreferences.getBoolean(s, false)) {
            cancelIntents();
            return;
        }

        // Get the time at which to schedule notifications.
        Calendar startTime = TimePreference.valueToCalendar(
                sharedPreferences.getInt(NOTIFICATION_TIME_PREF_KEY, 0));

        // If the time has already passed today, start notifications tomorrow.
        Calendar now = Calendar.getInstance();
        if (now.after(startTime)) {
            startTime.add(Calendar.DATE, 1);
        }

        // Build a notification, add it to the intent we'll schedule, and schedule it.
//        Notification notification = buildNotification();
//        scheduleNotification(notification, startTime);

        // Schedule an intent, and build and publish the notification in the future whenever
        // the intent is received.
        scheduleNotification(startTime);
    }

    private PendingIntent getNotificationPublisherIntent(Notification notification) {
        Intent intent = new Intent(this, NotificationPublisher.class);
        if (notification != null) {
            intent.putExtra(NotificationPublisher.NOTIFICATION, notification);
        }

        return PendingIntent.getBroadcast(
                this, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private Notification buildNotification() {
        Intent intent = new Intent(this, com.app.myapp.activity.MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_stat_notification)
                .setContentTitle("Hello I'm a notification!")
                .setContentText("Well look at that, it's content 111")
                .setContentIntent(pendingIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);

        return builder.build();
    }

    private void scheduleNotification(Notification notification, Calendar startTime) {
        scheduleIntent(getNotificationPublisherIntent(notification), startTime);
    }

    private void scheduleNotification(Calendar startTime) {
        scheduleIntent(getNotificationPublisherIntent(null), startTime);
    }

    private void scheduleIntent(PendingIntent intent, Calendar startTime) {
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, startTime.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY, intent);
    }

    private void cancelIntents() {
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(getNotificationPublisherIntent(null));
    }

    /**
     * Set up the {@link android.app.ActionBar}, if the API is available.
     */
    private void setupActionBar() {
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            // Show the Up button in the action bar.
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onIsMultiPane() {
        return isXLargeTablet(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }

    /**
     * This method stops fragment injection in malicious applications.
     * Make sure to deny any unknown fragments here.
     */
    protected boolean isValidFragment(String fragmentName) {
        return PreferenceFragment.class.getName().equals(fragmentName)
                || NotificationPreferenceFragment.class.getName().equals(fragmentName);
    }

    /**
     * This fragment shows notification preferences only. It is used when the
     * activity is showing a two-pane settings UI.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static class NotificationPreferenceFragment extends PreferenceFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_notification);
            setHasOptionsMenu(true);

            // Bind the summary of the TimePreference to its value. When the value changes, the
            // summary is updated to reflect the new value, per the Android Design guidelines.
            bindPreferenceSummaryToValue(findPreference("notifications_time"));
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == android.R.id.home) {
                startActivity(new Intent(getActivity(), SettingsActivity.class));
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
} 

TimePreference.java

public class TimePreference extends DialogPreference {

    // This is the same as the default value set in the XML. Keep them in sync.
    private static final int DEFAULT_TIME = 7 * 60;  // 0700

    // The currently chosen time stored as the number of minutes after midnight.
    private int time;

    private TimePicker picker;

    public TimePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public TimePreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TimePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TimePreference(Context context) {
        super(context);
    }

    /**
     * valueToSummary takes the raw value of the preference and converts it into a human-readable
     * string fit for use in e.g. the preference's summary.
     *
     * @param value The raw value of the preference.
     * @return The time formatted according to the current settings (locale, 12/24 hour clock)
     */
    public String valueToSummary(int value) {
        return DateFormat.getTimeFormat(getContext()).format(valueToCalendar(value).getTime());
    }

    public static Calendar valueToCalendar(int value) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, getHours(value));
        calendar.set(Calendar.MINUTE, getMinutes(value));
        calendar.set(Calendar.SECOND, 0);

        return calendar;
    }

    private void setTime(int minAfterMidnight) {
        time = minAfterMidnight;
        persistInt(time);
        notifyChanged();
    }

    private static int getHours(int minAfterMidnight) {
        return minAfterMidnight / 60;
    }

    private static int getMinutes(int minAfterMidnight) {
        return minAfterMidnight % 60;
    }

    @Override
    protected View onCreateDialogView() {
        picker = new TimePicker(getContext());
        return picker;
    }

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);

        picker.setIs24HourView(DateFormat.is24HourFormat(getContext()));
        if (Build.VERSION.SDK_INT >= 23) {
            picker.setHour(getHours(time));
            picker.setMinute(getMinutes(time));
        } else {
            picker.setCurrentHour(getHours(time));
            picker.setCurrentMinute(getMinutes(time));
        }
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            int hours, minutes;
            if (Build.VERSION.SDK_INT >= 23) {
                hours = picker.getHour();
                minutes = picker.getMinute();
            } else {
                hours = picker.getCurrentHour();
                minutes = picker.getCurrentMinute();
            }

            int newTime = hours * 60 + minutes;
            if (callChangeListener(newTime)) {
                setTime(newTime);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getInt(index, DEFAULT_TIME);
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        super.onSetInitialValue(restorePersistedValue, defaultValue);
        setTime(restorePersistedValue ? getPersistedInt(time) : (int) defaultValue);
    }
}

 

The receiver in my AndroidManifest.xml:

<receiver android:name=".activity.NotificationPublisher" /> 

I can set the time in my app and notifications are enabled for the app on the device. They simply don’t display.

Note: I am developing with Android API 29.

Answer

In the sample app, in NotificationSchedulerApplication:

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

private void createNotificationChannel() {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is new and not in the support library
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.channel_name);
        String description = getString(R.string.channel_description);
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
        channel.setDescription(description);
        // Register the channel with the system; you can't change the importance
        // or other notification behaviors after this
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}

The bit in the AndroidManifest.xml that causes onCreate to be called in the Application followed by createNotificationChannel is this:

<application
    android:name=".NotificationSchedulerApplication"
    .../>
</application>

onCreate in the Application should be called every time you do: Run -> Debug

Just click on the left of createNotificationChannel() to set a breakpoint.



Source: stackoverflow