Android Recurring Task Run In Background - java

I am having some problem when trying to do a recurring task in Android. Here is how I populate my list view:
public View getView(int position, View convertView, ViewGroup parent) {
String recurID;
if (convertView == null) {
convertView = inflater.inflate(R.layout.recur_listview_row, null);
viewHolder = new ViewHolder();
viewHolder.txt_ddate = (TextView) convertView.findViewById(R.id.txtDisplayRecurDate);
viewHolder.txt_damount = (TextView) convertView.findViewById(R.id.txtDisplayRecurAmount);
viewHolder.txt_dfrequency = (TextView) convertView.findViewById(R.id.txtDisplayFrequency);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
recurID = _recurlist.get(position).getRecurringID();
// Format and calculate the next payment date based on frequency
try {
String dateStr = _recurlist.get(position).getRecurringStartDate();
String frequencyStr = _recurlist.get(position).getFrequency();
Calendar cal = Calendar.getInstance();
cal.setTime(dateFormat.parse(dateStr));
if (frequencyStr.equals("Daily")) {
cal.add(Calendar.DAY_OF_MONTH, 1);
viewHolder.txt_ddate.setText("Next Payment On: " + dateFormat.format(cal.getTimeInMillis()));
cal.add(Calendar.DAY_OF_MONTH, -1);
} else if (frequencyStr.equals("Weekly")) {
cal.add(Calendar.WEEK_OF_YEAR, 1);
viewHolder.txt_ddate.setText("Next Payment On: " + dateFormat.format(cal.getTimeInMillis()));
cal.add(Calendar.WEEK_OF_YEAR, -1);
}
} catch (ParseException e) {
e.printStackTrace();
}
viewHolder.txt_dfrequency.setText(_recurlist.get(position).getFrequency().trim());
if (_recurlist.get(position).getRecurringType().equals("W")) {
viewHolder.txt_damount.setTextColor(Color.rgb(180, 4, 4));
viewHolder.txt_damount.setText("Credit $ " + amount);
} else if (_recurlist.get(position).getRecurringType().equals("D")) {
viewHolder.txt_damount.setTextColor(Color.rgb(8, 138, 8));
viewHolder.txt_damount.setText("Debit $ " + amount);
}
// Get current date
String currentDate = "Next Payment On: " + dateFormat.format(new Date());
// If current date matches with the next payment date, insert new
// transaction record
if (currentDate.equals(viewHolder.txt_ddate.getText())) {
DatabaseAdapter mDbHelper = new DatabaseAdapter(Recurring.this);
mDbHelper.createDatabase();
mDbHelper.open();
TransactionRecModel trm = new TransactionRecModel();
CategoryController cc = new CategoryController(mDbHelper.open());
trm.setDate(dateFormat.format(new Date()));
if (_recurlist.get(position).getRecurringType().equals("W")) {
trm.setType("W");
} else if (_recurlist.get(position).getRecurringType().equals("D")) {
trm.setType("D");
}
trm.setAmount(Float.parseFloat(formatAmount));
TransactionRecController trc = new TransactionRecController(mDbHelper.open());
if (trc.addTransactionRec(trm)) {
// After successfully insert transaction record, update the
// recurring start date
rm = new RecurringModel();
rm.setRecurringID(recurID);
rm.setRecurringStartDate(dateFormat.format(new Date()));
RecurringController rc = new RecurringController(mDbHelper.open());
if (rc.updateRecurringDate(rm)) {
mDbHelper.close();
}
}
}
return convertView;
}
From the code I tried to get the current date and compare with the next payment date computed based on frequency. However, with these code, it does not run in background.
Let's say I set a recurring event which will be repeating daily yesterday. But I did not run the application today. By right, the recurring should run in background and execute the recurring. But somehow, it does not.
I wonder do I need some service like AlarmManager to do this?
Thanks in advance.
EDIT
So what I've changed is the part when I try to compare the dates, if the dates matched, it will call the alarmManager and parse some values along the way:
if (currentDate.equals(viewHolder.txt_ddate.getText())) {
long when = new Date().getTime();
notificationCount = notificationCount + 1;
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent notificationIntent = new Intent(context, ReminderAlarm.class);
notificationIntent.putExtra("RecurID", recurID);
notificationIntent.putExtra("Date", dateFormat.format(new Date()));
notificationIntent.putExtra("Description", viewHolder.txt_ddesc.getText().toString());
notificationIntent.putExtra("Type", _recurlist.get(position).getRecurringType());
notificationIntent.putExtra("Amount", Float.parseFloat(formatAmount));
notificationIntent.putExtra("CategoryID", viewHolder.txt_dcat.getText().toString());
notificationIntent.putExtra("NotifyCount", notificationCount);
PendingIntent pi = PendingIntent.getBroadcast(context, notificationCount, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.set(AlarmManager.RTC_WAKEUP, when, pi);
}
And in my ReminderAlarm class, I am executing the insert and update SQL statement:
public class ReminderAlarm extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String recurID = intent.getStringExtra("RecurID");
String date = intent.getStringExtra("Date");
String description = intent.getStringExtra("Description");
String type = intent.getStringExtra("Type");
Float amount = Float.parseFloat(intent.getStringExtra("Amount"));
String categoryID = intent.getStringExtra("CategoryID");
DatabaseAdapter mDbHelper = new DatabaseAdapter(ReminderAlarm.this);
mDbHelper.createDatabase();
mDbHelper.open();
TransactionRecModel trm = new TransactionRecModel();
CategoryController cc = new CategoryController(mDbHelper.open());
trm.setDate(date);
trm.setTransDescription(description);
if (type.equals("W")) {
trm.setType("W");
} else if (type.equals("D")) {
trm.setType("D");
}
trm.setAmount(amount);
// Get the categoryID based on categoryName
String catID = cc.getCatIDByName(categoryID);
trm.setCategory(catID);
TransactionRecController trc = new TransactionRecController(mDbHelper.open());
if (trc.addTransactionRec(trm)) {
// After successfully insert transaction record, update the
// recurring start date
RecurringModel rm = new RecurringModel();
rm.setRecurringID(recurID);
rm.setRecurringStartDate(date);
RecurringController rc = new RecurringController(mDbHelper.open());
if (rc.updateRecurringDate(rm)) {
mDbHelper.close();
}
}
}
}

Your Adapter should receive data that is ready to be displayed. getView is called every time a ListView item comes onto screen, so you want to keep your work here to under 16ms if you hope to maintain 60fps scrolling. Because of this, you should do all heavy work before it gets to the Adapter.
Since database data is often not display-ready, you would typically use a Loader to get the data, and turn it into a list of "items" that are Adapter-ready. This should happen in your Activity or Fragment, and you fill the Adapter in onLoadFinished. This often means creating a new POJO to represent the display data.
Best place to start is the Loader tutorial.
If you want to set a recurring task, you should use the AlarmManager, as you suspected. The AlarmManager would typically trigger a BroadcastManager, which in turn would spawn a Service to do the work.
Follow the AlarmManager tutorial for more details.

Related

How to call a method from dbhelper in broadcastReceiver

I want to excute a method in my dbhelper immediately my alarm service run, the if that code runs successfully I want to display a notification based on the result of the method execution. I want this to run even if my app is idle or in the background
this is my broadcastReceiver
public class ExpiryBroadcast extends BroadcastReceiver {
//db helper
private DbHelper dbHelper;
private final String CHANNEL_ID = "expiring_items";
private final int NOTIFICATION_ID = 200;
#Override
public void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
dbHelper.updateExpiryRow();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_item_expiring)
.setContentTitle("ProExm Product Expiry")
.setContentText("Some products will soon expire, check now...")
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}
}
this is my alarmmanager
public void startAlertAtParticularTime() {
// alarm first vibrate at 14 hrs and 40 min and repeat itself at ONE_HOUR interval
intent = new Intent(this, ExpiryBroadcast.class);
pendingIntent = PendingIntent.getBroadcast(
this.getApplicationContext(), 280192, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 17);
calendar.set(Calendar.MINUTE, 02);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, pendingIntent);
Toast.makeText(this, "Alarm will vibrate at time specified",
Toast.LENGTH_SHORT).show();
}
I have this code in my dbhelper class which I want to run before notification even if my application is asleep or in the background
public void updateExpiryRow(){
int daysToExpiry = 0;
String selectQuery = "SELECT * FROM " + Constants.ITEMS_TABLE;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
//looping through all records and add to list
if(cursor.moveToFirst()){
do{
ModelItems modelItems = new ModelItems(
""+cursor.getInt(cursor.getColumnIndex(Constants.C_ID)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_ITEM_NAME)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_ITEM_IMAGE)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_ITEM_PRICE)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_ITEM_MANUFACTURER)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_DESC)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_EXPIRY_DATE)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_MANUFACTURE_DATE)),
Integer.parseInt(""+cursor.getInt(cursor.getColumnIndex(Constants.C_DAYS_TO_EXPIRY)))-1,
""+cursor.getString(cursor.getColumnIndex(Constants.C_ADDED_TIMESTAMP)),
""+cursor.getString(cursor.getColumnIndex(Constants.C_UPDATED_TIMESTAMP))
);
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("dd-MM-yyyy");
try {
Date date = formatter.parse(modelItems.getItemExp());
LocalDate now = LocalDate.now();
String text = now.format(formatter2);
Date dateNow = formatter.parse(text);
long diffInMillies = Math.abs(date.getTime() - dateNow.getTime());
long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
daysToExpiry = (int) (long) diff;
} catch (ParseException e) {
e.printStackTrace();
}
ContentValues values = new ContentValues();
// id will be inserted automatically as we set AUTOINCREMENT in query
//insert data
String timestamp = ""+System.currentTimeMillis();
values.put(Constants.C_ID, modelItems.getId());
values.put(Constants.C_ITEM_NAME, modelItems.getItemName());
values.put(Constants.C_ITEM_IMAGE, modelItems.getItemImage());
values.put(Constants.C_ITEM_PRICE, modelItems.getItemPrice());
values.put(Constants.C_ITEM_MANUFACTURER, modelItems.getItemManufacturer());
values.put(Constants.C_DESC, modelItems.getItemDesc());
values.put(Constants.C_EXPIRY_DATE, modelItems.getItemExp());
values.put(Constants.C_MANUFACTURE_DATE, modelItems.getItemMfd());
values.put(Constants.C_DAYS_TO_EXPIRY, daysToExpiry);
values.put(Constants.C_ADDED_TIMESTAMP, modelItems.getAddedTime());
values.put(Constants.C_UPDATED_TIMESTAMP, timestamp);
//insert row, it will return record id of saved record
db.update(Constants.ITEMS_TABLE, values, Constants.C_ID +" = ?", new String[] {modelItems.getId()});
//add record to list
}while (cursor.moveToNext());
}
//close db connection
db.close();
}
First of all you have to initialize the dbHelper
dbHelper = new DbHelper();
Then just call your method.
And this broadcast receiver will work if your app is idle or even closed
Try to use a foreground service and start it from your broadcast receiver, it will work even if the app is killed, and in your service, you can instantiate your DbHelper and execute what you want, after finishing tasks just stop it.

Adding an event to S Planner on Button Click

I have a problem - I am making an app, and I want to add a new event to User's Calendar (I have a S Planner so I'm testing on it) on button click. The problem is when it comes to date and time - it just doesn't change. Here's code:
bCalendar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent calintent = new Intent(Intent.ACTION_INSERT);
calintent.setType("vnd.android.cursor.item/event");
calintent.putExtra(CalendarContract.Events.TITLE, eventname);
calintent.putExtra(CalendarContract.Events.EVENT_LOCATION, city + ", " + street);
calintent.putExtra(CalendarContract.Events.DESCRIPTION, description);
calintent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, timestart);
startActivity(calintent);
}
});
timestart is a string which was passed through the other activity, for example "15:00".
I want to insert data from my activity into Samsung's S Planner. I have no problem with inserting everything except for date and time. Screenshot Nothing changes there. If you need any further details, please tell.
Managed to do it thanks to Selvin's pointing out my mistake.
Made a string containing date and time and converted it using SimpleDateFormat
to long.
bCalendar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent calintent = new Intent(Intent.ACTION_INSERT);
String dateString = datestart+" "+timestart;
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try{
Date d = f.parse(dateString);
long millis = d.getTime();
calintent.setType("vnd.android.cursor.item/event");
calintent.putExtra(CalendarContract.Events.TITLE, eventname);
calintent.putExtra(CalendarContract.Events.EVENT_LOCATION, city + ", " + street);
calintent.putExtra(CalendarContract.Events.DESCRIPTION, description);
calintent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, millis);
startActivity(calintent);
} catch(ParseException e){
e.printStackTrace();
}

Cancel an AlarmManager

I´ve got a method that sets the time I want an alarm to fire.
In this method I also got a Stop button that cancels the alarm
if(alarmManager != null){
alarmManager.cancel(pi);
}
My problem is that when I set the alarm, go out of the app and in again to cancel the alarm I get a nullPointer. Im guessing it is because the PendingIntent also closes when I leave the app (get set to null).
How can I prevent this from happening, so that I can cancel the alarm?
Heres the whole method:
#SuppressWarnings("deprecation")
public void setTime(){
Calendar mcurrentTime = Calendar.getInstance();
int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
int minute = mcurrentTime.get(Calendar.MINUTE);
TimePickerDialog mTimePicker;
mTimePicker = new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener()
{
int callCount = 0; //To track number of calls to onTimeSet()
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute)
{
if(callCount == 1) // On second call
{
String timeString = "";
timeString = selectedHour + ":" + selectedMinute + ":00";
Log.d("TEST", "Chosen time : "+ timeString);
setAlarm(timePicker, selectedHour, selectedMinute);
}
callCount++; // Incrementing call count.
}
}, hour, minute, true);
mTimePicker.setButton(DialogInterface.BUTTON_POSITIVE, "Set", mTimePicker);
mTimePicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Stop", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if(alarmManager != null){
alarmManager.cancel(pi);
}
}
});
mTimePicker.setTitle(R.string.time);
mTimePicker.show();
}
You can save the data that is used to construct your PendingIntent in a Bundle like shown here
So when you open up your app again, you will be able to reconstruct the PendingIntent and cancel the alarm if needed.
EDIT: First of all you have to override onSaveInstanceState and save the data that is used to create your PendingIntent object like shown below:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the pending intent id
savedInstanceState.putInt(PENDING_INTENT_ID, mPendingIntentId);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
After that, in your onCreate method you will check if the savedInstanceState is not null. If not null, restore the items from the savedInstanceState (Bundle) object like this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mPendingIntentId = savedInstanceState.getInt(PENDING_INTENT_ID);
} else {
// Probably initialize members with default values for a new instance
mPendingIntentId = YOUR_DEFAULT_INT_VALUE;
}
mPendingIntent = PendingIntent.getBroadcast(this, mPendingIntentId, YOUR_INTENT, PendindIntent.FLAG_UPDATE_CURRENT);
}
PendingIntent will be recreated every time you will enter your app, so you will be able to cancel the same alarm that has been triggered in a previous step.
EDIT 2 : Another approach in your case would be that since you have only one alarm, and it's id is always the same (which should be used for the PendingIntent's requestId), than you can simply recreate the PendingIntent whenever you want to add or cancel an alarm. A method like the one below would help you out.
private PendingIntent getPendingIntent(Context context){
return PendingIntent.getBroadcast(context, 0, new Intent(context, YourBroadcastReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT);
}

How to keep alive a JSONArray for widgets?

My app has a widget with two buttons that goes back and forth and displays information stored in a JSONArray. It works very well but the problem is when i close the app the widget stops working because the JSONArray becomes null. How can i keep it working even when the app is closed ?
its too long to post it here but this is the part when i click next button
if(intent.getAction().equals(NEXT_LISTING)){
try{
if (listingNumber !=15)
{
listingNumber++;
}
JSONObject latestListing = jsonListings.getJSONObject(listingNumber);
String titleString = latestListing.getString("title");
String descriptionString = latestListing.getString("description");
String categoryString = latestListing.getString("category");
Date postDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(latestListing.getString("created"));
Date newDate = new Date(postDate.getTime() + Utils.getCurrentTimezoneOffset() * (3600 * 100));
String createdDate = Utils.getFormattedDate(newDate);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.njoftime_widget);
views.setTextViewText(R.id.title, titleString);
views.setTextViewText(R.id.description, descriptionString);
views.setTextViewText(R.id.textView_category, categoryString);
views.setTextViewText(R.id.textView_date, createdDate);
for(int i=0; i < categoriesList.size();i++)
{
if(categoriesList.get(i).get(0).equals(latestListing.getString("category")))
{
views.setImageViewResource(R.id.category_icon_widget, iconList.get(i));
}
}
if(listingThumbnails.get(listingNumber) != null)
{
views.setViewVisibility(R.id.listing_photo, View.VISIBLE);
views.setImageViewUri(R.id.listing_photo, Uri.parse(""));
Bitmap roundedBitmap = getRoundedLeftCornerBitmap(listingThumbnails.get(listingNumber), 10);
views.setImageViewBitmap(R.id.listing_photo,roundedBitmap);
}
else
{
views.setViewVisibility(R.id.listing_photo, View.GONE);
}
// Instruct the widget manager to update the widget
ComponentName componentName = new ComponentName(context, NjoftimeWidget.class);
AppWidgetManager.getInstance(context).updateAppWidget(componentName, views);
}
catch (Exception e)
{
e.printStackTrace();
}
}

How to set an alarm for a selected date and time?

I have this code from where I can set a time and date from date picker and time picker at once:
private void dialoguetime() {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.custom_dialogue);
dialog.setCancelable(true);
dialog.setTitle("This is Dialog 1");
dialog.show();
TimePicker time_picker = (TimePicker) dialog
.findViewById(R.id.timePicker1);
hours = time_picker.getCurrentHour();
minute = time_picker.getCurrentMinute();
time_picker.setOnTimeChangedListener(new OnTimeChangedListener() {
public void onTimeChanged(TimePicker view, int hourOfDay,
int minutes) {
// TODO Auto-generated method stub
// Toast.makeText(CustomDialog.this,
// "hourOfDay = "+hourOfDay+"minute = "+minute , 1000).show();
hours = hourOfDay;
minute = minutes;
}
});
final DatePicker date_picker = (DatePicker) dialog
.findViewById(R.id.datePicker1);
Button btn = (Button) dialog.findViewById(R.id.button2);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
// TODO Auto-generated method stub
xDate = date_picker.getYear() + "-"+ (date_picker.getMonth() + 1) + "-"+ date_picker.getDayOfMonth() + " " + hours+ ":" + minute + ":00";
Toast.makeText(
getApplicationContext(),
xDate, Toast.LENGTH_LONG)
.show();
dialog.cancel();
}
}
);
}
From this I can get a string as a date format like this yyyy-mm-dd hh:mm:ss, now I want to give an alert (as alarm) to the user of that selected time. I have used alarm manager for this but it didn't allow me to select that date?
How can I do this?
If you look at the API for AlarmManager (http://developer.android.com/reference/android/app/AlarmManager.html) you can see that the method
set()
requires the following parameters:
public void set (int type, long triggerAtMillis, PendingIntent operation)
where
int type = One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or RTC_WAKEUP.
long triggerAtMillis = time in milliseconds that the alarm should go off, using the appropriate clock (depending on the alarm type).
PendingIntent operation = Action to perform when the alarm goes off
So basicly what you should do, is to get the time between the selected date (DateFormat.parse() to parse the date-formated string to date) and the current date. Something like this:
Date now = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");
Date parsedDate = format.parse(YOUR_PICKED_DATE_AS_STRING);
long alertTime = parsedDate.getTime() - now.getTime();
Intent someIntent = new Intent(getApplicationContext(), YourClass.class);
PendingIntent pending = PendingIntent.getBroadcast(getApplicationContext(), 0, someIntent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.

Categories