0

Problem summary: I have a listView which stores a list of vocabulary in WordsToMemorize Class, a NotificationWorker Class and a NotificationReceiver Class for processing notifications. My aim is to tap an item (which for example is a word called "baseball") of the listView, and the item will fire a timePicker where I will set a specific time (say, 5 munites later) to fire a notification. The notification, when appearing, is supposed to show a title that goes:" Do you remeber this word: baseball ? ". The notification works properly when I fire only one notification, but if I fire a second notification immediately for another word like "apple", the title of first the second notification would both become " Do you remeber this word: apple? ". I mean by the time both notification comes up, the first one should be for "baseball" and the second one should be for "apple". The problem is that "baseball" has been replaced when I fired a second notification.

So is there a way to make notifications remember their specifics words?

Things I have tried: I tried to set a different notification id for each notification and that didn't work. I thought of generating a time tag fetching the current system time when "baseball" (or any other item in the listView) is tapped, and letting the notification manager to pair the time tag and "baseball", so that will be the identifier for "baseball" and hence the notification can remember the word. However this is much above my coding level and I didn't know how to achieve this in codes.

Below are my codes for reference:

WordsToMemorize Class

    public class WordsToMemorize extends AppCompatActivity {

        static String vocabularyToBeMemorized;

        Calendar c;
        WorkManager mWorkManager;


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.words_to_memorize);

            final ListView myVocabularyListview;
            final ArrayAdapter myVocabularyArrayAdapter;


            //findViewById
            myVocabularyListview = findViewById(R.id.my_vocabulary_listview);



            mWorkManager = WorkManager.getInstance();


            //Initialize the adapter
            myVocabularyArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, myVocabularyArrayList);
            myVocabularyListview.setAdapter(myVocabularyArrayAdapter);



            /**
             * Let the user click on an item and set notification timings
             */
            myVocabularyListview.setOnItemClickListener(new AdapterView.OnItemClickListener(){

                public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {

                    final String selectedMyVocabularyListviewItemValue=myVocabularyListview.getItemAtPosition(position).toString();

                    AlertDialog.Builder AlertDialog = new AlertDialog.Builder(WordsToMemorize.this);
                    AlertDialog .setTitle(getString(R.string.Choose_the_timing_to_recall_a_word));
                    AlertDialog .setCancelable(false); 
                    AlertDialog .setView(R.layout.custom_alert_dialog_dictionary_providers);

                    //Time Picker Button
                    AlertDialog .setPositiveButton(getString(R.string.Customize_timing), new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {

//I couldn't directly fetch the values of the myVocabularyListview items so I had to pass them to a dummy texView called "wordInputView" and then fetch them from there.
MainActivity.wordInputView.setText(selectedMyVocabularyListviewItemValue);
                            setCustomizedNotificationTiming();
                        }
                    });


                    //Cancel Button
                    AlertDialog .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });

                    AlertDialog .create();
                    AlertDialog .show();

                }
            });



        @Override
        protected void onStart() {
            super.onStart();

            c = Calendar.getInstance(); 
        }




        // Helper Method

        public void setCustomizedNotificationTiming() {

            vocabularyToBeMemorized = MainActivity.wordInputView.getText().toString();

            // on Time
            new TimePickerDialog(this,
                    new TimePickerDialog.OnTimeSetListener() {
                        @Override
                        public void onTimeSet(TimePicker view, int hourOfDay,
                                              int minute) {
                            c.set(Calendar.HOUR_OF_DAY, hourOfDay);
                            c.set(Calendar.MINUTE, minute);

                            long nowMillis = System.currentTimeMillis();  
                            long millis = c.getTimeInMillis() - nowMillis; 

                            if (c.before(Calendar.getInstance())) {        
                                Toast.makeText(getApplicationContext(), getString(R.string.Hey_thats_too_early),Toast.LENGTH_LONG).show();

                            } else {                                       
                                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.TAIWAN);
                                Long scheduledDateInMilliSeconds=c.getTimeInMillis();
                                String FormattedScheduledDate = dateFormat.format(scheduledDateInMilliSeconds);
                                Toast.makeText(getApplicationContext(), getString(R.string.Will_send_the_notification_at) + FormattedScheduledDate + getString(R.string.blank_space),Toast.LENGTH_LONG).show();


                                OneTimeWorkRequest UserDefinedNotificationRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class)
                                        .addTag("UserDefinedNotificationTag" + " for " + vocabularyToBeMemorized)
                                        .setInitialDelay(millis, TimeUnit.MILLISECONDS)
                                        .build();
                                mWorkManager.enqueue(UserDefinedNotificationRequest);

                            }

                        }
                    },
                    c.get(Calendar.HOUR_OF_DAY),
                    c.get(Calendar.MINUTE),
                    false).show();

            // on Date
            new DatePickerDialog(this,
                    new DatePickerDialog.OnDateSetListener() {
                        @Override
                        public void onDateSet(DatePicker view, int year,
                                              int monthOfYear, int dayOfMonth) {

                            c.set(Calendar.YEAR, year);
                            c.set(Calendar.MONTH, monthOfYear);
                            c.set(Calendar.DAY_OF_MONTH, dayOfMonth);

                        }
                    },
                    c.get(Calendar.YEAR),
                    c.get(Calendar.MONTH),
                    c.get(Calendar.DAY_OF_MONTH)).show();

        }


    }

NotificationWorker Class

public class NotificationWorker extends Worker {


    public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {

        showNotification("Hey I'm your worker", "Work is done");

        return Result.success();

    }


    public int createID(){
        Date now = new Date();
        int id = Integer.parseInt(new SimpleDateFormat("ddHHmmssSS",  Locale.TAIWAN).format(now));
        return id;
    }


    private void showNotification(String task, String desc) {

        RemoteViews collapsedNotificationView = new RemoteViews(getApplicationContext().getPackageName(),R.layout.custom_notification_normal_view);
                    collapsedNotificationView.setTextViewText(R.id.normal_notification_title,"Do you Remember this word:" + WordsToMemorize.vocabularyToBeMemorized + "?");
        RemoteViews expandedNotificationView = new RemoteViews(getApplicationContext().getPackageName(),R.layout.custom_notification_expanded_view);
                    expandedNotificationView.setTextViewText(R.id.expanded_notification_title,"Do you Remember this word:" + WordsToMemorize.vocabularyToBeMemorized + "?");

        Intent resultIntent = new Intent(getApplicationContext(), MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(getApplicationContext());

        stackBuilder.addParentStack(MainActivity.class);

        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent =
                stackBuilder.getPendingIntent(
                        0,
                        PendingIntent.FLAG_UPDATE_CURRENT
                );


        Intent broadcastIntent = new Intent(getApplicationContext(), NotificationReceiver.class);
        broadcastIntent.putExtra("vocabularyToBeMemorized", WordsToMemorize.vocabularyToBeMemorized);
        PendingIntent actionIntent = PendingIntent.getBroadcast(getApplicationContext(),
                0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);


        NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            NotificationChannel channel = new
                    NotificationChannel("simplfiedcoding", "simplfiedcoding", NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), "simplfiedcoding")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLights(Color.YELLOW , 1000 , 1000) 
                .setColor(Color.BLUE)
                .setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true) 
                .setOnlyAlertOnce(true)
                .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
                .setCustomContentView(collapsedNotificationView)
                .setCustomBigContentView(expandedNotificationView)
                .setContentIntent(resultPendingIntent)
                .addAction(R.mipmap.dictionary,"yes",actionIntent);


        int id = createID();
        manager.notify(id, builder.build());

    }

}

NotificationReceiver Class

public class NotificationReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(final Context context, Intent intent) {
        final String vocabularyToBeMemorizedFromNotification = intent.getStringExtra("vocabularyToBeMemorized");

        Intent launchMainActivityIntent = context.getPackageManager().getLaunchIntentForPackage("com.example.android.dictionaryalmighty2");
        if (launchMainActivityIntent != null) {
            context.startActivity(launchMainActivityIntent);//null pointer check in case package name was not found
        }


        // This is only for null pointer errors before the app is fully launched and loaded
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            public void run() {

                Intent intent = new Intent(context.getApplicationContext(), ComboSearchActivity.class);
                context.startActivity(intent);


                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    public void run() {


                        // Just plain methods to look up dictionaries
                        loadFirstDefaultDictionaries();
                        loadSecondDefaultDictionaries();
                        loadThirdDefaultDictionaries();

                    }
                }, 1000);

            }
        }, 1000);   //1 second delay

    }
Paulo Pedroso
  • 3,555
  • 2
  • 29
  • 34
Dean
  • 119
  • 5

3 Answers3

1

As suggested in my comment, you could use a LinkedList (which will function as a queue) as such:

Replace the

static String vocabularyToBeMemorized;

in your WordsToMemorize class with:

static LinkedList vocabulariesToBeMemorized = new LinkedList<String>(); 

Then, instead of setting the static String to a value, add the value to the list as such:

vocabulariesToBeMemorized.add(MainActivity.wordInputView.getText().toString());

And simply replace

final String vocabularyToBeMemorizedFromNotification = intent.getStringExtra("vocabularyToBeMemorized");

with

String wordToMemorize = "";
if (WordsToMemorize.vocabulariesToBeMemorized.peek() != null) {
     wordToMemorize = vocabulariesToBeMemorized.poll();
}

and use that in your builder to set the title, with

builder.setTitle(wordToMemorize);

Hope it helps!

EDIT: please use

static LinkedList<String> vocabulariesToBeMemorized = new LinkedList<>();

Also, I actually tried to suggest you to not use any intent.putExtra() or intent.getStringExtra(), but replace it with the static LinkedList that functions as a queue. If you do so, and set the title in your showNotification() method with wordToMemorize, I think it might work.

Community
  • 1
  • 1
Jorn Rigter
  • 745
  • 1
  • 6
  • 25
  • Thanks Jorn. There's a new problem though. Now every notification title becomes " Do you remeber this word: null ? ". What I did was replacing "static String vocabularyToBeMemorized" to "static LinkedList vocabulariesToBeMemorized = new LinkedList()", replacing "vocabularyToBeMemorized = MainActivity.wordInputView.getText().toString()" to "final String vocabularyToBeMemorizedFromNotification = intent.getStringExtra("vocabularyToBeMemorized")". At this point a warning came up saying "vocabulariesToBeMemorized.add" is an unchecked call, and I ignored it. – Dean Dec 13 '19 at 20:30
  • For the last step I replaced "final String vocabularyToBeMemorizedFromNotification = intent.getStringExtra("vocabularyToBeMemorized")" to your suggested code. At this point was a warning saying "Incompatible types. Required string but found object", so I cast to string as such: "wordToMemorize = (String) vocabulariesToBeMemorized.poll()". Then a waring came up saying "variable wordToMemorize is assinged but never accessed". In the Handler section, I changed "MainActivity.wordInputView.setText(vocabularyToBeMemorizedFromNotification)" to "MainActivity.wordInputView.setText(wordToMemorize)" – Dean Dec 13 '19 at 20:44
  • and copied wordToMemorize to final temp variable or else Android Studio would nag that wordToMemorize need to be declared final. The resulting Handler section now becomes: Handler handler = new Handler(); final String finalWordToMemorize = wordToMemorize; handler.postDelayed(new Runnable() { public void run() { MainActivity.wordInputView.setText(finalWordToMemorize); – Dean Dec 13 '19 at 20:48
  • I'm actually using two customized layouts, one for the normal and the other for the expanded notification view; hence the existence of RemoteViews in NotificationWorker Class where I set the title of notification. But anyways I still added "builder.setTitle(wordToMemorize);" as you suggested and the aforementioned new problem still exists. Are there further revisions we need to make? – Dean Dec 13 '19 at 21:02
  • Hi Dean, I updated my answer a bit. The Object you got was because the LinkedList didn't have an explicit type, my bad. With LinkedList vocanulariesToBeMemorized it will have – Jorn Rigter Dec 14 '19 at 09:59
  • Also, I think my suggestion was not clear enough. What I meant to say was to replace the intent.putExtra() part and intent.getStringExtra() with the Queue. Is it clearer like this? Let me know if you have any results! – Jorn Rigter Dec 14 '19 at 10:00
  • Oh my god Jorn, it's starting to work like a charm. By that I mean the notitfications don't have the same title anymore (I added an addAction code to the notification builder to show the notitfication titles in log). The catch is that the titles don't appear in the notifications. The titles are blank (which is why I had to use addAction to know the titles). I need the titles to appear to tell which notification is which. On the other hand, I can't quite comprehend your suggestion on replacing the intent part with Queue. Would you kindly show me in codes? – Dean Dec 14 '19 at 12:38
  • I'll try to write you a small sample application, just a moment! – Jorn Rigter Dec 15 '19 at 15:02
0

I think it might have to do with the ID of the notification indeed. You could try this solution proposed by @sagar.android:

Simple notification_id needs to be changable.

Just create random number for notification_id.

Random random = new Random();
int m = random.nextInt(9999 - 1000) + 1000; 

or you can use this method for creating random number as told by tieorange (this will never get repeated):

int m = (int) ((new Date().getTime() / 1000L) % Integer.MAX_VALUE); and replace this line to add parameter for

notification id as to generate random number

notificationManager.notify(m, notification);

Let me know if it works!

Jorn Rigter
  • 745
  • 1
  • 6
  • 25
  • Hello Jorn. Unfortnately this didn't work out. Actually I've already used a time stamp method to generate notification id as the system time, which is "public int createID()" in NotificationWorker Class. The 1st and 2nd notifications came up separatelty but showed the same title which is undesirable. My guess is the problem lies in "broadcastIntent.putExtra("vocabularyToBeMemorized", WordsToMemorize.vocabularyToBeMemorized);" in NotificationWorker Class. The "WordsToMemorize.vocabularyToBeMemorized" string in WordsToMemorize Class gets replaced with new string when new notification is fired. – Dean Dec 13 '19 at 17:08
  • I figured whenever a new notification is fired, that string has to be stored somehow and when a following notification is fired, that string will be replaced with a new one which too has to be stored. The challenge is that the notification manager has to tell which string is for which notification and make sure a notification is paired with a correct string. By the way, that string is the value of myVocabularyListview item. I'm just trying hard to find the pain spot. Correct me if I'm looking at the wrong direction. – Dean Dec 13 '19 at 17:18
  • Ah, I see now indeed. I think you are looking into the right direction. I would suggest using a Queue to store the words you want to use in your notifications. The delay of the notifications is the same all the time right? You could use a LinkedList to implement this, please see my answer below. – Jorn Rigter Dec 13 '19 at 18:01
0

I tried making a small application for you that demonstrates what I mean. Now, I was unsure as to how long the waiting time to post a new notification could be, if the time is one week for example, you could have a look at the AlarmManager (as suggested here) instead of using a Handler as I am doing now.

Now, on to the application. This is a minimum working example with two buttons, "Butterfly" and "Baseball":

MainActivity with the two buttons.

public class MainActivity extends AppCompatActivity {

    Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button baseballBtn = findViewById(R.id.baseball);
        baseballBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                postDelayedNotification(baseballBtn);
            }
        });

        final Button butterflyBtn = findViewById(R.id.butterfly);
        butterflyBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                postDelayedNotification(butterflyBtn);
            }
        });
    }

    private void postDelayedNotification(Button button) {
        final String message = "Do you remember this word: " + button.getText();

        if (handler == null) {
            handler = new Handler();
        }
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // Do something after 5s = 5000ms
                MyNotificationManager.showNotification(getApplicationContext(), message);
            }
        }, 3000); // delay for how long you want (you can use the set calendar here)
    }
}

Custom NotificationManager for handling notifications:

class MyNotificationManager {

    public static void showNotification(Context applicationContext, String message) {
        NotificationManager manager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);

        if (manager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel("simplfiedcoding", "simplfiedcoding", NotificationManager.IMPORTANCE_DEFAULT);
                manager.createNotificationChannel(channel);
            }

            // In this builder, you could set your actionIntents etc. as you had in your application
            NotificationCompat.Builder builder = new NotificationCompat.Builder(applicationContext, "simplfiedcoding")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentTitle("New Message")
                    .setContentText(message)
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT);

            int uniqueId = (int) ((new Date().getTime() / 1000L) % Integer.MAX_VALUE);
            manager.notify(uniqueId, builder.build());
        }
    }

}

I tested this application and it seems to be working fine!

Jorn Rigter
  • 745
  • 1
  • 6
  • 25
  • I suppose you are suggesing to fire an notification with a button and fetch the text of the button and set it as the title of the notification. That makes total sense. In my case, because I need to tap an item of the vocabulary list (namely "myVocabularyListview") to create a notification, I might have to add a button below the list, and set the text of the button as the text of the tapped list item, and then set the notification title as the text of the button. One question though, if I consecutively quick tap all items of the list in one shot, which puts all items on notification queue, – Dean Dec 15 '19 at 16:57
  • and if each of those items have to create notifications at multiple timings (after 1 day, 1 week and 1 month), would that mess up the text of the button and the title of notification, which means the notifications would come up with wrong titles?The cores I need for my desired function to work is to use a vocabulary list, a notification manager, (probably a button as you suggested), and multiple timings to fire notifications. I need to tap on any one word of my vocabulary list, and let the app to notify me of re-memorizing after 1 day, 1 week and 1 month. I wonder if this wish is plausible LOL – Dean Dec 15 '19 at 17:08
  • Hey Dean. For notifying after 1 day, week or month, I would suggest to use the AlarmManager instead (as suggested in my post). Regarding the onClick method, you can set an onClickListener on your listView item, and retrieve the text from that particular item, no need for any buttons or anything luckily for you. Clicking multiple items would in this case be no problem. If the answer helped you, show some love ;) – Jorn Rigter Dec 16 '19 at 17:34