How do i fix my code to make the smallicon of the notification work?

Even when i set a small icon in my Alarm Manager extension with an image from Android Asset Studio it still shows a circle, white in light mode, black in night mode. Normally you would put the hdpi or mdpi file in the res/drawable folder of the project, but in Mit App Inventor this isn't possible. Here's the code:

package com.damiano.alarm;

import android.app.AlarmManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
import com.google.appinventor.components.runtime.util.MediaUtil;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

public class AlarmManagerExtension extends AndroidNonvisibleComponent {

    private static AlarmManagerExtension instance;
    private final Context context;

    public AlarmManagerExtension(ComponentContainer container) {
        super(container.$form());
        this.context = container.$context();
        instance = this;
    }

    public static AlarmManagerExtension getInstance() {
        return instance;
    }

    @SimpleFunction(description = "Set an exact alarm at a specified date and time with a custom notification. Date format should be 'yyyy-MM-dd HH:mm'.")
    public void SetAlarm(String dateTime, String notificationTitle, String notificationText, String notificationIcon) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
        try {
            Date date = format.parse(dateTime);
            if (date != null) {
                long triggerAtMillis = date.getTime();
                int id = (int) (System.currentTimeMillis() % Integer.MAX_VALUE);

                AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                Intent intent = new Intent(context, AlarmReceiver.class);
                intent.putExtra("NOTIFICATION_TITLE", notificationTitle);
                intent.putExtra("NOTIFICATION_TEXT", notificationText);
                intent.putExtra("NOTIFICATION_ICON", notificationIcon);
                PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_IMMUTABLE);

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
                } else {
                    alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
                }

                showToast("Alarm set for: " + dateTime + " with ID: " + id);
            }
        } catch (ParseException e) {
            showToast("Failed to parse dateTime: " + dateTime);
        }
    }

    @SimpleFunction(description = "Set a repeating alarm every day at the specified time with a custom notification. Time format should be 'HH:mm'.")
    public void SetRepeatingAlarm(String time, String notificationTitle, String notificationText, String notificationIcon) {
        SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
        try {
            Date date = timeFormat.parse(time);
            if (date != null) {
                Calendar calendar = Calendar.getInstance();
                calendar.set(Calendar.HOUR_OF_DAY, date.getHours());
                calendar.set(Calendar.MINUTE, date.getMinutes());
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);

                long triggerAtMillis = calendar.getTimeInMillis();
                if (System.currentTimeMillis() > triggerAtMillis) {
                    triggerAtMillis += AlarmManager.INTERVAL_DAY;
                }

                int id = (int) (System.currentTimeMillis() % Integer.MAX_VALUE);

                AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                Intent intent = new Intent(context, AlarmReceiver.class);
                intent.putExtra("NOTIFICATION_TITLE", notificationTitle);
                intent.putExtra("NOTIFICATION_TEXT", notificationText);
                intent.putExtra("NOTIFICATION_ICON", notificationIcon);
                PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_IMMUTABLE);

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
                } else {
                    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_DAY, pendingIntent);
                }

                showToast("Repeating alarm set for: " + time + " every day with ID: " + id);
            }
        } catch (ParseException e) {
            showToast("Failed to parse time: " + time);
        }
    }

    @SimpleFunction(description = "Cancel an alarm with the specified ID.")
    public void CancelAlarm(int id) {
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, AlarmReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_IMMUTABLE);

        alarmManager.cancel(pendingIntent);
        showToast("Alarm cancelled with ID: " + id);
    }

    @SimpleFunction(description = "Request permission to post notifications on Android 13 and above.")
    public void AskNotificationsPermission() {
        if (Build.VERSION.SDK_INT >= 33) {
            form.requestPermissions(new String[]{"android.permission.POST_NOTIFICATIONS"}, 7);
        }
    }

    @SimpleFunction(description = "Request permission to schedule exact alarms on Android 12 and above.")
    public void AskScheduleExactAlarmPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            form.requestPermissions(new String[]{"android.permission.SCHEDULE_EXACT_ALARM"}, 8);
        }
    }

    @SimpleEvent(description = "Triggered when the alarm goes off.")
    public void OnAlarm() {
        showToast("OnAlarm event triggered");
        EventDispatcher.dispatchEvent(this, "OnAlarm");
    }

    private void showToast(String message) {
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }

    private Bitmap getBitmapFromAsset(String assetName) {
        try {
            return MediaUtil.getBitmapDrawable(form, assetName).getBitmap();
        } catch (IOException e) {
            showToast("Failed to load image: " + assetName);
            return null;
        }
    }

    private void sendNotification(String title, String text, String iconName) {
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        String channelId = "alarm_channel_id";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId, "Alarm Channel", NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
                .setContentTitle(title)
                .setContentText(text)
                .setSmallIcon(android.R.drawable.ic_dialog_info);  // Fallback small icon

        if (iconName != null && !iconName.isEmpty()) {
            Bitmap iconBitmap = getBitmapFromAsset(iconName);
            if (iconBitmap != null) {
                builder.setLargeIcon(iconBitmap);
            }
        }

        notificationManager.notify((int) System.currentTimeMillis(), builder.build());
    }
}

is this java

yes

@Taifun i saw AlarmManager does this, but how? Do you mind giving a brief explanation?

can u upload it to github so i can check it my self

My understanding of the AlarmManager API is that it doesn't show the notification, it simply sends the PendingIntent to your app and then at that point you'd need to show a notification.

The notification is sent, the small icon is simply white, it means that it was not applied correctly. Normally you should load an icon for the notification in the res/drawable or r/drawable folder of your project, but this folder is obviously not available in MIT App Inventor, I saw that @Taifun had addressed this problem but I have no idea how to fix it.

I'm sorry, but I will not reveal the code of how I'm doing it at this time. I put a lot of work into the extension. It is available to purchase for 15 USD only.

The latest version 10 not only offers to display a notification at alarm time, but now also offers to open your app even above the lock screen in case the device is locked.

Taifun

1 Like

btw, i would buy your extension, probably right away, if you set up a stripe link, it's literally international.

but not for Costa Rica
Paypal and Bitcoin are 100% international

Taifun



:face_with_monocle:

Sorry, but no

If you really want to buy the extension, then you find a way to purchase it via Paypal or Bitcoin

Taifun

Gatekeeping push notifications to Paypal users is crazy :sob::pray:

Btw, how much time didi it take you to implement custom small icon? Because it seems impossible😭

android.R.drawable.ic_dialog_info is a circle with the letter i inside. Do you see a different icon? What is the minimum API of your app? Since API 23 it is possible to set smallIcon from an icon:

Bitmap bitmap = getBitmapFromAsset(iconName);
Icon icon = Icon.createWithBitmap(bitmap);
builder.setSmallIcon(icon);

I have no problem with the icons already present in the resources, the problem was that I always run into the grey circle bug when trying to set a custom icon, now I will try your suggestion. Thank you!


We got it!

Btw the picture doesn't even need to be small, this image is 500x500. but i haven't tested colors yet.


Small updates.

Now the app will also open on notification click.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.