How to update widget with data from Activity? - android

I want to update widget with data from my Activity every second. I have Item which has timers and its updating by onBind method sending payload to it. Every time it will update data and send new (Strings, Dates, Color resources) to payload, I want to update existing Widget with these data. I should update it every second as timer goes on. (payload is sent every second) Is this possible? Because I can add my data to extras and send Intent to AppWidgetProvider, but I don't know how to update views in Widget with new data sent into onReceive. Tried this code in onUpdate function. Which should be called every 1000ms which I set in xml for widget provider - android:updatePeriodMillis="1000". What is happening is Grey box on my homescreen with Problem loading widget text. No crashes or errors were present in logs.
RecyclerView item:
private fun updateWidgetTimer(text: String, color: Int){
val int = Intent(a, WidgetProvider::class.java)
int.apply {
putExtra(WidgetProvider.REM_TIME, text)
putExtra(WidgetProvider.REM_TIME_COLOR, color)
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
}
}
AppWidgetProvider:
private var time: String = ""
private var color: Int = R.color.colorGreen
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
val remoteViews = RemoteViews(context?.packageName, R.layout.widget_custom_layout)
remoteViews.setTextViewText(R.id.timer, time)
context?.let {
remoteViews.setTextColor(R.id.timer, ContextCompat.getColor(context, color))
}
}
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
val extras = intent?.extras
extras?.let {
time = extras.getString(REM_TIME)?:""
color = extras.getInt(REM_TIME_COLOR)
}
}
UPDATE:
Payload is successfully send to onReceive, but as onUpdate is called, none of my widgets are updated with new data.
Example of my updated onUpdate function:
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
context?.let {
val currWidgetInstanceName = ComponentName(context, WidgetProvider::class.java)
val widgetManager = AppWidgetManager.getInstance(context)
val widgetIds = widgetManager.getAppWidgetIds(currWidgetInstanceName)
widgetIds?.forEach {wId->
val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout)
remoteViews.apply {
//set info title
setTextViewText(R.id.title, title)
//set subtitle
setTextViewText(R.id.subT, subt)
//set info
setTextViewText(R.id.info, info)
//set rem timer
setTextViewText(R.id.outTimer, remTime)
setTextColor(R.id.outTimer, ContextCompat.getColor(context, remTimeColor))
}
widgetManager?.updateAppWidget(currWidgetInstanceName, remoteViews)
}
}
}

From https://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo
Note: Updates requested with updatePeriodMillis will not be delivered
more than once every 30 minutes.
If you want frequent updates, you should use
AlarmManager
Use a service and update the widget, example:
Intent intent = new Intent(context, WidgetUpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarm.cancel(pending);
alarm.setRepeating(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime(), 1000, pendingIntent);

AppWidgetProvider.onUpdate is called by AppWidgetProvider.onReceive. Call super.onReceive(context, intent) at the end of onReceive method. Data will be updated before onUpdate will be called.
override fun onReceive(context: Context?, intent: Intent?) {
val extras = intent?.extras
extras?.let {
time = extras.getString(REM_TIME)?:""
color = extras.getInt(REM_TIME_COLOR)
}
super.onReceive(context, intent)
}

Related

How to set data in fillInIntent

I have a list view in my home screen widget and I want to launch a pending intent when the item is clicked. I referred to this and set a fillInIntent for every list item and then set a pending intent template which triggers the broadcast receiver of the widget:
class WidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
val todosStr = widgetData.getString("todos", "null")
val todos = ArrayList<HashMap<String, Any>>()
val todosRemoteView = RemoteViews.RemoteCollectionItems.Builder()
if(todosStr != "null"){
val jObj = JSONObject(todosStr)
val jsonArry = jObj.getJSONArray("todos")
for (i in 0 until jsonArry.length()) {
val todo = HashMap<String, Any>()
val obj = jsonArry.getJSONObject(i)
todo["id"] = obj.getInt("id")
todos.add(todo)
val view = RemoteViews(context.packageName, R.layout.each_todo).apply {
setTextViewText(R.id.each_todo_container_text, todo["taskName"].toString())
val fillInIntent = Intent().apply {
Bundle().also { extras ->
extras.putInt("todo_id", todo["id"].toString().toInt())//this isn't working for some reason
putExtras(extras)
}
}
Log.d("debugging", "id received is ${todo["id"].toString()}" )
setOnClickFillInIntent(R.id.each_todo_container_text, fillInIntent)
}
todosRemoteView.addItem(todo["id"].toString().toInt().toLong(), view)
}
}
setRemoteAdapter(
R.id.todos_list,
todosRemoteView
.build()
)
val pendingIntentx: PendingIntent = Intent(
context,
WidgetProvider::class.java
).run {
PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_IMMUTABLE or Intent.FILL_IN_COMPONENT)
}
setPendingIntentTemplate(R.id.todos_list, pendingIntentx)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
override fun onReceive(context: Context?, intent: Intent?) {
val viewIndex: Int = intent!!.getIntExtra("todo_id", 0)
Log.d("debugging", "an item is clicked $viewIndex")
//the default value gets used, which means that the receiver isn't receiving the intent data
super.onReceive(context, intent)
}
}
The problem is that the receiver isn't receiving the data which was put in the fillInIntent, the default value 0 is getting printed by the last statement. I also tried setting the FILL_IN_COMPONENT flag as suggested here, but that didn't work. Where am I going wrong?

cancel intent service from broadcast broadcastreceiver

I'm sending a local notification with a cancel button and when I click it, I want to cancel my intent service but the event is not getting called.
I can cancel the intent service using an activity, but when is from a notification, it is not working.
My BroadcastReceiver (the Log is showing).
class EventsReceiver: BroadcastReceiver(){
var onEvent = {}
override fun onReceive(context: Context?, intent: Intent?) {
Log.i("EventsReceiver", "onReceive")
when(intent?.action){
Constants.ACTION_EVENT_CANCEL -> {
Log.i("EventsReceiver", "cancel")
onEvent()
}
}
}
}
The onReceive is getting called when I click the notification button but when I call the onEvent(), the cancelTrip() function is not getting called.
My Intent Service:
override fun onCreate() {
super.onCreate()
notificationHelper = NotificationHelper(this)
receiver = EventsReceiver ()
receiver.onEvent = {
cancelTrip()
}
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(Constants.ACTION_EVENT_CANCEL))
}
The activity function that cancels the intent service:
private fun cancel(){
val intent = Intent()
intent.setAction(Constants.ACTION_EVENT_CANCEL)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
How I create my local notification:
val notif = NotificationCompat.Builder(ctx,NOTIFICATION_CHANNEL_ID)
notif.setContentTitle("My App")
notif.setContentText("Canceled trip")
notif.setSmallIcon(R.drawable.ic_launcher_foreground)
val notifIntent = Intent(ctx, MenuActivity::class.java)
val pendingIntent = PendingIntent.getActivity(ctx,0,notifIntent,0)
notif.setContentIntent(pendingIntent)
val cancelIntent = Intent(ctx, EventsReceiver::class.java)
val cancelPendingIntent = PendingIntent.getBroadcast(ctx,0,cancelIntent,PendingIntent.FLAG_CANCEL_CURRENT)
notif.addAction(R.mipmap.ic_launcher, ctx.getString(R.string.cancel_trip), cancelPendingIntent)
service.notify(NOTIFICATION_CANCEL_ID, notif.build())
val cancelIntent = Intent(ctx, EventsReceiver::class.java)
It's because you didn't specify the action (while creating the notification) so in here when(intent?.action)
you have different action then Constants.ACTION_EVENT_CANCEL.
try using this:
val cancelIntent = Intent(Constants.ACTION_EVENT_CANCEL)
or
val cancelIntent = Intent(ctx, EventsReceiver::class.java)
cancelIntent.action = Constants.ACTION_EVENT_CANCEL

How can I differentiate which action has been clicked in a notification?

I'm making and Android app that sends notifications with actions several times a day, the problem is that at this moment doesn't matter which action the user clicks it always sends the first intent to the broadcast receiver.
My code:
fun sendNotification(title: String, content: String, tomaID: Int){
val takeShotIntent = Intent(context, TreatmentBroadcastReceiver::class.java).apply {
putExtra("TomaID", tomaID)
putExtra("AcctionToma", 0)
}
val takeShotPendingIntent = PendingIntent.getBroadcast(context, NOTIFICACION_ID, takeShotIntent, PendingIntent.FLAG_ONE_SHOT)
val skipShotIntent = Intent(context, TreatmentBroadcastReceiver::class.java).apply {
putExtra("TomaID", tomaID)
putExtra("AcctionToma", 1)
}
val skipShotPendingIntent = PendingIntent.getBroadcast(context, NOTIFICACION_ID, skipShotIntent, PendingIntent.FLAG_ONE_SHOT)
val postPoneShotIntent = Intent(context, TreatmentBroadcastReceiver::class.java).apply {
putExtra("TomaID", tomaID)
putExtra("AcctionToma", 2)
}
val postPoneShotPendingIntent = PendingIntent.getBroadcast(context, NOTIFICACION_ID, postPoneShotIntent, PendingIntent.FLAG_ONE_SHOT)
val notifyBuilder = getNotificationBuilder(title,content)
notifyBuilder.addAction(R.drawable.ic_capsula, context.getString(R.string.tomar), takeShotPendingIntent)
notifyBuilder.addAction(R.drawable.ic_capsula,context.getString(R.string.saltar), skipShotPendingIntent)
notifyBuilder.addAction(R.drawable.ic_capsula,context.getString(R.string.posponer), postPoneShotPendingIntent)
mNotifyManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mNotifyManager.notify(NOTIFICACION_ID, notifyBuilder.build())
}
And the broadcast receiver class:
class TreatmentBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val idToma = intent.getIntExtra("TomaID", -1)
val acctionToma = intent.getIntExtra("AcctionToma", -1)
Log.d("EstasReciviendo", idToma.toString() + " " + acctionToma)
}
}
I need to send an ID and a number that represents what the app should do based on the user selection. The ID is sent without problems but as a I mention above the "AccionToma" is always 0 in the onReceive method no matter which action is tapped.
My logcat output:
2019-07-21 17:09:10.993 20286-20286/com.kps.spart.moskimedicationreminder D/EstasReciviendo: 1049 0
So,
How can I differentiate which action has been clicked?
Use different unique values, instead of NOTIFICACION_ID, for your three PendingIntent.getBroadcast() calls.
As it stands, your second PendingIntent.getBroadcast() call replaces the first one, and the third PendingIntent.getBroadcast() call replaces the second.

How to Repeat a function every Hour in Android Studio using Kotlin?

I want to repeat a function( or any action ) every one Hour ( for example ) even if the app is not running.
I've created a demo project so you can take a look at it :
https://github.com/joancolmenerodev/BroadcastReceiverAndAlarmManagerInKotlin
You first have to create a BroadcastReceiver, and then using AlarmManager you can decide the interval of time you want to be called.
Create a BroadcastReceiver you can do it as follows :
val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
toast("This toast will be shown every X minutes")
}
}
And then you have this method to start the job :
val mIntent = Intent(context, broadCastReceiver)
val mPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, mIntent, 0)
val mAlarmManager = context
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP, System.currentTimeMillis(),
CHANGETOYOURDESIREDSECONDS, mPendingIntent
)
And then you'll be able to see the Toast even if the app is closed.
Edit
You can register your BroadcastReceiver using context.registerReceiver(receiver, IntentFilter("something"))
and then adding to the mIntent and action for "something".
If you don't like this way, you can create a new class named MyReceiver that extends BradcastReceiver as follows :
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"This toast will be shown every X minutes", Toast.LENGTH_SHORT).show()
}
}
And then start the alarm doing this :
val mIntent = Intent(this, MyReceiver::class.java)
val mPendingIntent = PendingIntent.getBroadcast(this, 0, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val mAlarmManager = this
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager.setRepeating(
AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
WHATEVERYOUWANT, mPendingIntent
)
Note: By default is set to 60000
Value will be forced up to 60000 as of Android 5.1; don't rely on this to be exact
If you are using AndroidX(JetPack) library then concider to use Workmanager
Simple example:
public class MyWorker extends Worker {
static final String TAG = "workmng";
#NonNull
#Override
public WorkerResult doWork() {
Log.d(TAG, "doWork: start");
//Do your job here
Log.d(TAG, "doWork: end");
return WorkerResult.SUCCESS;
}
}
And start like this, to do your job each hour :
PeriodicWorkRequest myWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 60, TimeUnit.MINUTES)
.build();
Add in app gradle file:
dependencies {
def work_version = 2.0.0
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
}

Prompt to unlock lock screen on custom notification click

I have a custom notification shown in the lock screen. I am using a broadcast pending intent to send a message to my app once the notification is clicked. Which later starts an activity in the broadcast receiver.
The problem is, once I click on the notification, it disappears from the lock-screen and the activity is launched behind the lock-screen. It does not ask the user to unlock the screen.
My requirement is to ask the user to unlock the screen as soon as the broadcast is sent by the notification's click event. How do I do that?
I could find this question which looks like my problem. But unfortunately there is no answer.
Here is some code that explains the notification creation I did.
/**
* Method to render Notification
*/
private void showNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
/* set mandatory stuff on builder */
Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification.bigContentView = createCustomView(context);
}
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(getNotificationId(), notification);
}
/**
* Method to return notification click's PendingIntent
*/
private PendingIntent getNotificationClickIntent() {
Intent bcIntent = new Intent(OPEN_APP);
bcIntent.putExtra(EXTRA_DEEP_LINK_URL, getDeepLinkUrl());
return PendingIntent.getBroadcast(
context, getReqCode(), bcIntent, PendingIntent.FLAG_ONE_SHOT);
}
/**
* Method to create custom view for notification
*/
private RemoteViews createCustomView(Context context) {
RemoteViews customView = new RemoteViews(context.getPackageName(), R.layout.custom_layout);
if (customView != null) {
// set dat on views
customView.setOnClickPendingIntent(R.id.my_view, getNotificationClickIntent());
}
return customView;
}
Use Activity intent in pending intent instead of Service or Broadcast
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Update:
In my case i use service.
private fun activityPendingIntent(context: Context, action: String? = null): PendingIntent {
Timber.d("activityPendingIntent")
val intent = Intent(context, DummyBackgroundActivity::class.java)
action?.let { intent.action = action }
return PendingIntent.getActivity(context, ACTIVITY_NOTIFICATION_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT)
}
DummyBackgroundActivity
class DummyBackgroundActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val service = Intent(this, BackgroundService::class.java)
service.action = intent.action
startService(service)
}
override fun onResume() {
super.onResume()
finish()
}
}
Service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.d("onStartCommand intent = %s", intent?.action)
when (intent?.action) {
//Handle it
}
return Service.START_NOT_STICKY
}
I hope you replicate the same using Broadcast.

Categories

Resources