I am running a worker in a glance composable on android.
why is onReceive being continuously called in an infinite loop?
What am I missing here?
class MyWidget : GlanceAppWidget() {
#Composable
override fun Content() {
val work = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
WorkManager.getInstance().enqueue(work)
}
}
class MyWorker(
private val context: Context,
private val workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
return Result.success()
}
}
class GlanceReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = MyWidget()
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
}
}
//Dependencies
implementation "androidx.work:work-runtime-ktx:2.7.1" // WorkManager with Coroutines
implementation "androidx.glance:glance-appwidget:1.0.0-alpha03" //Glance
That's because WM is disabling the on boot receiver when there are no workers scheduled. When an android app disables a receiver Android OS sends the PACKAGE_CHANGED broadcast event, causing the widget onReceive to be called.
https://issuetracker.google.com/115575872
For now the recommendation is to schedule a work with a long delay (e.g 10 years). We are working on a way to improve this.
Related
I have an app which has a home screen widget via GlanceAppWidget().
I would like to run a worker inside the Content() function of GlanceAppWidget(). I have used enqueue(work) from WorkManager api to successfully execute my worker.
The problem is that onReceive is getting called multiple (infinite) number of times. How can I run the worker once without having onReceive called multiple times?
class MyWidget : GlanceAppWidget() {
#Composable
override fun Content() {
val work = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
WorkManager.getInstance().enqueue(work)
//some composable/ui code that consumes worker output
}
}
class MyWorker(
private val context: Context,
private val workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
return try {
startForegroundService()
//some task here
Result.Success.success(
workDataOf(
"MyKey" to "worker completed successfully"
)
)
} catch (throwable: Throwable) {
Result.failure()
}
}
private suspend fun startForegroundService() {
setForeground(
ForegroundInfo(
Random.nextInt(),
NotificationCompat.Builder(context, "download_channel")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentText("Downloading...")
.setContentTitle("Download in progress")
.build()
)
)
}
}
class GlanceReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = MyWidget()
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
}
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
}
I am working on app in which first i have to start the process and then update the value so how to access other methods of custom method o workmanager thanks
class SmsWorkManager(val context : Context, workerParameters:WorkerParameters) : CoroutineWorker(context ,workerParameters) {
override suspend fun doWork(): Result {
println("do some task ")}
fun updateMethod(){
println("how to access this method")}
}
// class Instannce for work maanager
val workManager = WorkManager.getInstance(this )
// val oneTimeRequest =OneTimeWorkRequest.Builder(SmsWorkManager::class.java)
workManager.enqueue(oneTimeRequest.build())
You need to return a Result after work completion & you can simply use the updateMethod() inside your `doWork() like below:
class SmsWorkManager(val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
println("do some task ")
updateMethod()
return Result.success()
}
fun updateMethod(){
println("how to access this method")
}
}
Also, if you are not doing any IO task then you should use a Worker instead of a CoroutineWorker.
I schedule one time work in 100 minutes:
val request = OneTimeWorkRequestBuilder<MyWorker().setInitialDelay(100, TimeUnit.MINUTES).build()
WorkManager.getInstance(context).enqueueUniqueWork(
"MY_WORKER_NAME",
ExistingWorkPolicy.REPLACE,
request
)
class MyWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(
context, workerParameters) {
override suspend fun doWork(): Result {
return Result.success()
}
How can I get the remaining time, before doWork() is executed?
For example, I need to know it immediately after I reboot my Android device
I have a ContentProvider from a main app. The content will be shared with a consumer app. This consumer app has an app widget. I have tested the ContentProvider and ContentObserver to this consumer app in its Activity and all is well (meaning the RecyclerView of the Activity is updated whenever an update from the main app triggers changes to the database). However, registering the ContentObserver inside my AppWidgetProvider does not work as expected.
My AppWidgetProvider has the following code.
class StackWidgetProvider : AppWidgetProvider() {
override fun onEnabled(context: Context) {
Timber.i("Enabled")
if (favoriteUserProviderObserver == null) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val componentName = ComponentName(context, StackWidgetProvider::class.java)
favoriteUserProviderObserver = FavoriteUserProviderObserver(appWidgetManager, componentName).let {
context.contentResolver.registerContentObserver(CONTENT_URI, true, it)
it
}
}
}
override fun onDisabled(context: Context) {
Timber.i("Disabled")
favoriteUserProviderObserver?.let {
context.contentResolver.unregisterContentObserver(it)
}
favoriteUserProviderObserver = null
}
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
....
companion object {
private var favoriteUserProviderObserver: FavoriteUserProviderObserver? = null
private fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val intent = Intent(context, StackWidgetService::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
data = toUri(Intent.URI_INTENT_SCHEME).toUri()
}
val views = RemoteViews(context.packageName, R.layout.widget_favorite_user_stack).apply {
setRemoteAdapter(R.id.widget_favorite_user_stack_view, intent)
setEmptyView(R.id.widget_favorite_user_stack_view, R.id.widget_favorite_user_empty)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
I created a simple custom ContentObserver class like below.
class FavoriteUserProviderObserver(
private val appWidgetManager: AppWidgetManager,
private val componentName: ComponentName
) : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
Timber.i("Provider observer triggered")
appWidgetManager.notifyAppWidgetViewDataChanged(
appWidgetManager.getAppWidgetIds(componentName), R.id.widget_favorite_user_stack_view
)
}
}
The above observer class is never triggered (even when I change the data in my main app). For further clarity, here's the code for my RemoteViewsService and its factory.
class StackWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
StackRemoteViewsFactory(this.applicationContext)
}
class StackRemoteViewsFactory(private val context: Context) :
RemoteViewsService.RemoteViewsFactory {
private var widgetItems = listOf<UserProfileSummary>()
private lateinit var repository: FavoriteUserRepository
override fun onCreate() {
repository = FavoriteUserRepository(
FavoriteUserDataSource(context.contentResolver),
Dispatchers.IO
)
}
override fun onDataSetChanged() {
GlobalScope.launch {
widgetItems = repository.favoriteUsers().toList() // Tested; working on the Activity scope of the consumer app
Timber.i(widgetItems.toString())
}
}
override fun getViewAt(position: Int): RemoteViews =
RemoteViews(context.packageName, R.layout.widget_favorite_user_item).apply {
setTextViewText(R.id.widget_favorite_user_item_text, widgetItems[position].username)
}
override fun getLoadingView(): RemoteViews? = null
override fun getItemId(position: Int): Long = 0
override fun hasStableIds(): Boolean = false
override fun getCount(): Int {
return widgetItems.size
}
override fun getViewTypeCount(): Int = 1
override fun onDestroy() {}
}
So the logic is to ask the ContentObserver to observe changes in the ContentProvider. The observer is registered on the onEnabled and onDisabled part of the AppWidgetProvider. Once the observer notices a change in ContentProvider, it will ask the AppWidgetProvider to update itself, thus calling onDataSetChanged and fetching a new list of data.
However, the observer is never called. What could be the reason it's not working as expected here? (It can't be because of a lack of permission, because the Activity part of the consumer app is able to fetch the data just fine.)
What could be the reason it's not working as expected here?
An AppWidgetProvider is a subclass of BroadcastReceiver. Your instance of AppWidgetProvider will live for (hopefully) a very short time, best measured in milliseconds. Basically, you get one onUpdate() call (or other callback), and that instance is thrown away. The next callback gets a new instance.
As a result, doing anything in an AppWidgetProvider that requires it to be around for a period of time is doomed.
The most likely solution, taking modern versions of Android into account, is to adopt more of a push solution. Bear in mind that any of your code can update the RemoteViews for an app widget, simply by working with AppWidgetManager. So, some code that is already running and knows about the data updates needs to push a new RemoteViews, rather than expecting your AppWidgetProvider to be able to react to changes.
Lets say that i have an activity that starts a worker. inside the worker i do a pseudo suspend proccess and then i print out a result from the database. Here is the code
The activity which starts the worker is
class SplashActivity: BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val oneTimeRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).setInputData(Data.Builder().apply {
putInt("data", 1)
}.build()).addTag("worktag").build()
WorkManager.getInstance(applicationContext).enqueue(oneTimeRequest)
}
}
The worker is the below
class MyWorker #AssistedInject constructor(
#Assisted private val appContext: Context,
#Assisted private val params: WorkerParameters,
private val serverRepository: ServerRepository
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
GlobalScope.launch {
for (i in 0..10) {
println("$i")
delay(1000)
}
val servers = serverRepository.getServers()
runOnUiThread {
Toast.makeText(appContext, "${servers.firstOrNull()?.serverAddress}", Toast.LENGTH_SHORT).show()
}
}
return Result.success()
}
}
So the result is that i see in the logcat the system.out with 1,2,3... and then i see a toast messages.
However, when i totally kill the app from the recent while the counter still counts, i never see the toast message.
Why is this happening since i have a GlobalScope coroutine?
And what is the right way to do this??
I was trying to achieve a similar goal. I managed my work by using ForegroundService.
You can find more here
https://developer.android.com/guide/components/foreground-services