Status Update
Comments
ra...@google.com <ra...@google.com> #2
su...@google.com <su...@google.com> #3
dr...@gmail.com <dr...@gmail.com> #4
dr...@gmail.com <dr...@gmail.com> #5
Currently the combination of Hilt, SavedStateHandle and Safe args is not possible. The closest I've seen is to drop using safe args and just use magic strings. This also not ideal re dependency inversion as now you have to construct a mock SavedStateHandle for unit tests etc.
class ArticleViewModel @ViewModelInject constructor(
private val repository: ArticleRepository,
@Assisted savedStateHandle: SavedStateHandle
) {
// FIXME: oops, magic "articleId" string
private val articleId: Long = savedStateHandle["articleId"]
?: throw IllegalArgumentException("Article ID required")
}
dr...@gmail.com <dr...@gmail.com> #6
I thought I needed this, but then I realized that I can just create an empty Bundle, store the Fragment's arguments in it as a Bundle (so a Bundle inside a Bundle) and use that to initialize the SavedStateHandle. Then I can just retrieve the arguments from the SavedStateHandle and use the already existing fromBundle
method.
What I like about this is that I get to have immutable init arguments, as long as I store the Fragment's arguments with a key that I would never use for other entries.
With a helper function and a base ViewModel class you can hide this entire process and have ViewModels that expose the arguments object as property/member of the ViewModel itself.
dr...@gmail.com <dr...@gmail.com> #7
Could you provide an example of how you achieve that?
su...@google.com <su...@google.com> #8
What I wrote requires the ability to provide a Bundle
to initialize the SavedStateHandle
and currently that's not possible with Hilt. You need to wait for this to be fixed:
If you do not use Hilt, then what I wrote becomes straightforward. Rather than passing the Fragment's arguments directly, you provide a Bundle
with the Fragment's arguments in it:
Bundle().apply { putBundle(SOME_SPECIAL_BUNDLE_KEY, arguments) }
By doing so you'll be able to do the following in your ViewModel
:
val fragmentArgs = savedStateHandle[SOME_SPECIAL_BUNDLE_KEY]
val args = YouFragmentArgs.fromBundle(fragmentArgs)
You still need to use a magic key for the Bundle, but it can be a project-wide constant and you only have to pass a single argument. The initialization is also the same for all the ViewModels, so you can easily write helpers to abstract away the process.
ra...@google.com <ra...@google.com> #9
If you are using hilt you can wrap the incoming arguments in fragment like so
open class BaseFragment : Fragment() {
override fun setArguments(args: Bundle?) {
if (args != null) {
super.setArguments(Bundle(args).apply {
putBundle(BUNDLE_ARGS, args) // Wrap the arguments as BUNDLE_ARGS
})
} else {
super.setArguments(null)
}
}
}
and then for the view model, you can inherit from something like that
open class ArgsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val arguments get() = savedStateHandle.get<Bundle>(BUNDLE_ARGS)
@MainThread
inline fun <reified Args : NavArgs> navArgs() = NavArgsLazy(Args::class) {
arguments ?: throw IllegalStateException("ViewModel $this has null arguments")
}
}
Hope that helps
dr...@gmail.com <dr...@gmail.com> #11
Do you know when this will be released?
su...@google.com <su...@google.com> #12
Re #11 - The public hotlist listed at the top of this bug says "androidx-navigation-2.4.0-alpha01" - that's the expected release.
dr...@gmail.com <dr...@gmail.com> #13
It seems this has been included in the
But is there any additional setup required besides updating the navigation dependencies? With the following dependencies on my project
object Navigation {
private const val version = "2.4.0-alpha01"
const val compose = "androidx.navigation:navigation-compose:$version"
const val fragment = "androidx.navigation:navigation-fragment-ktx:$version"
const val safeArgsPlugin = "androidx.navigation:navigation-safe-args-gradle-plugin:$version"
const val ui = "androidx.navigation:navigation-ui-ktx:$version"
}
my safeargs don't generate fromSavedStateHandle()
.
fromBundle()
is still the only auto-complete suggestion.
FooFragmentArgs.fromSavedStateHandle(savedStateHandle)
is an unresolved reference in my ViewModel.
su...@google.com <su...@google.com> #14
Android Studio : 4.2.1
com.android.tools.build:gradle:4.2.1
androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha01
Is there any thing we can do about this?
dr...@gmail.com <dr...@gmail.com> #15
Re #13, #14 - please upgrade to the latest version of Android Studio and file a bug against Android Studio if you are still not seeing this method in the Studio generated light classes.
dr...@gmail.com <dr...@gmail.com> #16
I'm using Arctic Fox | 2020.3.1 Canary 9. I've just updated Navigation to 2.4.0-alpha02 and still seeing the bug, so I've filed a bug against AS.
dr...@gmail.com <dr...@gmail.com> #17
As mentioned in the linked issue, manually downloading the latest version of Android Studio Preview fixes the issue
su...@google.com <su...@google.com> #18
dr...@gmail.com <dr...@gmail.com> #19
The interest bit starts from "01-29 20:51:44.715"
ra...@google.com <ra...@google.com> #20
Can you send us a trimmed down sample of how Workers are being enqueued for your app ?
ra...@google.com <ra...@google.com> #21
dr...@gmail.com <dr...@gmail.com> #22
static void schedulePeriodicWork(int appWidgetId, String trigger) {
Log.d(TAG, "schedulePeriodicWork for " + appWidgetId);
String name = getPeriodicName(appWidgetId);
long interval = getInterval();
Constraints constraints = getConstraints("periodic");
Data inputData = getPeriodicInputData(appWidgetId, trigger);
PeriodicWorkRequest workRequest = getPeriodicWorkRequest(appWidgetId, interval, constraints, inputData);
WorkManager workManager = WorkManager.getInstance();
workManager.enqueueUniquePeriodicWork(name, ExistingPeriodicWorkPolicy.REPLACE, workRequest);
}
private static String getPeriodicName(int appWidgetId) {
return "periodic-" + appWidgetId;
}
private static long getInterval() {
return 15 * 60 * 1000;
}
private static Constraints getConstraints(String method) {
Log.d(TAG, "creating Constraints for " + method);
Constraints.Builder constraintsBuilder = new Constraints.Builder();
Log.d(TAG, "adding network constraint");
constraintsBuilder.setRequiredNetworkType(NetworkType.CONNECTED);
return constraintsBuilder.build();
}
private static PeriodicWorkRequest getPeriodicWorkRequest(int appWidgetId, long interval, Constraints constraints, Data inputData) {
String workTag = getWorkTag(appWidgetId);
Log.d(TAG, "creating PeriodicWorkRequest with tag " + workTag + " and with interval " + interval + " ms");
PeriodicWorkRequest.Builder workRequestBuilder = new PeriodicWorkRequest.Builder(MeteogramWorker.class, interval, TimeUnit.MILLISECONDS);
workRequestBuilder.setConstraints(constraints);
workRequestBuilder.setInputData(inputData);
workRequestBuilder.addTag(workTag);
workRequestBuilder.addTag(PERIODIC);
return workRequestBuilder.build();
}
private static Data getPeriodicInputData(int appWidgetId, String trigger) {
Log.d(TAG, "creating Data");
Data.Builder dataBuilder = new Data.Builder();
dataBuilder.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
dataBuilder.putString(trigger);
dataBuilder.putString("method", "periodic");
return dataBuilder.build();
}
private static String getWorkTag(int appWidgetId) {
return "widget-" + appWidgetId;
}
dr...@gmail.com <dr...@gmail.com> #23
01-29 21:07:01.245 W/dalvikvm(29185): Unable to resolve superclass of Landroidx/work/impl/background/systemjob/SystemJobService; (90)
01-29 21:07:01.245 W/dalvikvm(29185): Link of class 'Landroidx/work/impl/background/systemjob/SystemJobService;' failed
01-29 21:07:01.245 E/dalvikvm(29185): Could not find class 'androidx.work.impl.background.systemjob.SystemJobService', referenced from method androidx.work.impl.Schedulers.createBestAvailableBackgroundScheduler
01-29 21:07:01.245 W/dalvikvm(29185): VFY: unable to resolve const-class 2971 (Landroidx/work/impl/background/systemjob/SystemJobService;) in Landroidx/work/impl/Schedulers;
01-29 21:07:01.345 W/dalvikvm(29185): Unable to resolve superclass of Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback; (341)
01-29 21:07:01.345 W/dalvikvm(29185): Link of class 'Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback;' failed
01-29 21:07:01.350 E/dalvikvm(29185): Could not find class 'androidx.work.impl.constraints.trackers.NetworkStateTracker$NetworkStateCallback', referenced from method androidx.work.impl.constraints.trackers.NetworkStateTracker.<init>
01-29 21:07:01.350 W/dalvikvm(29185): VFY: unable to resolve new-instance 2994 (Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback;) in Landroidx/work/impl/constraints/trackers/NetworkStateTracker;
01-29 21:07:01.355 I/dalvikvm(29185): Could not find method android.net.ConnectivityManager.getActiveNetwork, referenced from method androidx.work.impl.constraints.trackers.NetworkStateTracker.isActiveNetworkValidated
01-29 21:07:01.355 W/dalvikvm(29185): VFY: unable to resolve virtual method 1865: Landroid/net/ConnectivityManager;.getActiveNetwork ()Landroid/net/Network;
01-29 21:07:01.355 D/dalvikvm(29185): VFY: replacing opcode 0x6e at 0x000a
01-29 21:07:01.360 W/dalvikvm(29185): Unable to resolve superclass of Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback; (341)
01-29 21:07:01.360 W/dalvikvm(29185): Link of class 'Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback;' failed
01-29 21:07:01.360 I/dalvikvm(29185): Could not find method android.net.ConnectivityManager.registerDefaultNetworkCallback, referenced from method androidx.work.impl.constraints.trackers.NetworkStateTracker.startTracking
01-29 21:07:01.360 W/dalvikvm(29185): VFY: unable to resolve virtual method 1871: Landroid/net/ConnectivityManager;.registerDefaultNetworkCallback (Landroid/net/ConnectivityManager$NetworkCallback;)V
01-29 21:07:01.365 W/dalvikvm(29185): Unable to resolve superclass of Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback; (341)
01-29 21:07:01.365 W/dalvikvm(29185): Link of class 'Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback;' failed
01-29 21:07:01.365 I/dalvikvm(29185): Could not find method android.net.ConnectivityManager.unregisterNetworkCallback, referenced from method androidx.work.impl.constraints.trackers.NetworkStateTracker.stopTracking
01-29 21:07:01.365 W/dalvikvm(29185): VFY: unable to resolve virtual method 1872: Landroid/net/ConnectivityManager;.unregisterNetworkCallback (Landroid/net/ConnectivityManager$NetworkCallback;)V
01-29 21:07:01.365 D/dalvikvm(29185): VFY: replacing opcode 0x6e at 0x0018
01-29 21:07:01.370 W/dalvikvm(29185): Unable to resolve superclass of Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback; (341)
01-29 21:07:01.370 W/dalvikvm(29185): Link of class 'Landroidx/work/impl/constraints/trackers/NetworkStateTracker$NetworkStateCallback;' failed
01-29 21:07:01.370 D/dalvikvm(29185): DexOpt: unable to opt direct call 0x593e at 0x17 in Landroidx/work/impl/constraints/trackers/NetworkStateTracker;.<init>
dr...@gmail.com <dr...@gmail.com> #24
01-29 21:11:33.875 D/Meteogram_ConfigActvty(29556): ********** LOG OF WORK QUEUE **********
01-29 21:11:34.175 D/Meteogram_Worker(29556): workInfo: WorkInfo{mId='8eaea234-7001-479e-ae88-2d628f57a62b', mState=ENQUEUED, mOutputData=androidx.work.Data@0, mTags=[widget-43443, com.cloud3squared.meteogram.MeteogramWorker, periodic]}
01-29 21:11:34.175 D/Meteogram_Worker(29556): workInfo: WorkInfo{mId='c43771c7-6b71-49fb-a2ee-c8486b37b798', mState=SUCCEEDED, mOutputData=androidx.work.Data@0, mTags=[widget-43443, com.cloud3squared.meteogram.MeteogramWorker, onetime]}
01-29 21:11:34.175 D/Meteogram_ConfigActvty(29556): ********** END OF WORK QUEUE **********
The above is logged by the following function, with the widget id passed in to get the tag (same method as in previous post), and filtering the work on this tag:
static void logWorkQueue(int appWidgetId) {
String tag = getWorkTag(appWidgetId);
WorkManager workManager = WorkManager.getInstance();
ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfosByTag(tag);
try {
List<WorkInfo> workInfoList = workInfos.get();
for (WorkInfo workInfo : workInfoList) {
Log.d(TAG, "workInfo: " + workInfo.toString());
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
dr...@gmail.com <dr...@gmail.com> #25
ra...@google.com <ra...@google.com> #26
I have a pending CL as well. It should be fixed in the next beta.
dr...@gmail.com <dr...@gmail.com> #27
Any ETA on the next beta?
Keep up the good work on this really useful library!
ap...@google.com <ap...@google.com> #28
Branch: androidx-master-dev
commit 177837514245d5bb24cc8e9bc27f20503e23e308
Author: Rahul Ravikumar <rahulrav@google.com>
Date: Tue Jan 29 17:50:14 2019
Fixes ConstraintsCommandHandler that correctly enables and disables proxies.
* ConstraintsCommandHandler was only looking at work that was eligible to be scheduled.
This is incorrect, as we may have Workers that are scheduled, but are pending execution due
to an unmet constraint. We need to look at all scheduled work which is unfinished.
* The only reason why this worked before with integration tests was due to one of 2 reasons:
* The process was in the foreground anyway, and there was an active WorkConstraintTracker.
* Enabling / disabling airplane mode (to test Network Constraints) would have caused a
TIME_SET broadcast in some cases where we reschedule everything (RescheduleReceiver).
Test: Updated SystemAlarmDispatcherTests & integration tests on API 22 with the Battery charging
Constraint that has no other side effects.
Change-Id: Ib973931b47be915dd8f533e1e651b6c1a29843f9
Fixes:
M work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
M work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
M work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
M work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
dr...@gmail.com <dr...@gmail.com> #29
ra...@google.com <ra...@google.com> #30
dr...@gmail.com <dr...@gmail.com> #31
dr...@gmail.com <dr...@gmail.com> #32
Are the tests you ran when fixing this before still working? Or has something subsequently messed it up? Or maybe I'm seeing another issue altogether.
su...@google.com <su...@google.com> #33
dr...@gmail.com <dr...@gmail.com> #34
su...@google.com <su...@google.com> #35
dr...@gmail.com <dr...@gmail.com> #36
dr...@gmail.com <dr...@gmail.com> #37
02-15 17:37:31.535 D/My_Worker(11599): workInfo: WorkInfo{mId='00db2ea7-07d6-42a1-a506-a26ff8e3217d', mState=ENQUEUED, mOutputData=androidx.work.Data@0, mTags=[com.example.myapp.MyWorker, widget-43806, periodic]}
02-15 17:37:31.535 D/My_Worker(11599): workInfo: WorkInfo{mId='08bce448-5d4a-4a17-89d2-6fdce6ef1091', mState=SUCCEEDED, mOutputData=androidx.work.Data@0, mTags=[com.example.myapp.MyWorker, widget-43806, backstop]}
And the only reason I can even tell if the work item is periodic or not is because I tag it with 'periodic' if it is. Otherwise, all I get is a work ID and a state... would be really handy to know when it last ran, when it is due to run next, etc. Would be very useful not least for debugging purposes.
As suggested:
ra...@google.com <ra...@google.com> #38
ra...@google.com <ra...@google.com> #39
It always goes back to ENQUEUED for the next interval.
dr...@gmail.com <dr...@gmail.com> #40
ra...@google.com <ra...@google.com> #41
dr...@gmail.com <dr...@gmail.com> #42
dr...@gmail.com <dr...@gmail.com> #43
su...@google.com <su...@google.com> #44
dr...@gmail.com <dr...@gmail.com> #45
dr...@gmail.com <dr...@gmail.com> #46
ra...@google.com <ra...@google.com> #47
dr...@gmail.com <dr...@gmail.com> #48
There seems to be lots of "D/WM-" stuff in there that refers to constraints... hopefully something of interest.
dr...@gmail.com <dr...@gmail.com> #49
To see if it made a difference, I went back to NOT using a flexInterval, i.e. using:
workRequestBuilder = new PeriodicWorkRequest.Builder(MeteogramWorker.class, interval, TimeUnit.MILLISECONDS);
INSTEAD of:
workRequestBuilder = new PeriodicWorkRequest.Builder(MeteogramWorker.class, interval, TimeUnit.MILLISECONDS, flexInterval, TimeUnit.MILLISECONDS);
And without using flexInterval, behaviour seems to be FAR more reliable so far. The periodic work (with network constraint) has so far survived some degree of testing (on the user device): letting the periodic work fire a few times, then turning off network for one or more scheduled firings, then network back on... and each time so far the "missed" work runs straight away.
It's far from an extensive test yet, but it seems like it can't be coincidence... the above change (to NOT use flexInterval) is all I've changed, and it is performing better so far.
After *this current* issue (i.e. #123379508) was marked fixed on 30 Jan, a change was made to WorkManager flexInterval behaviour on 13 Feb as per the following:
Would it have anything to do with that? Between #123379508 being fixed (30 Jan) and #124274584 being fixed (13 Feb), I think that things were going well. It seemed to be after #124274584 was fixed that things went downhill again. Again, I can't be certain, but it's a lead anyway.
P.S. the only reason I started using flexInterval in the first place was to address the issue reported here:
dr...@gmail.com <dr...@gmail.com> #50
ra...@google.com <ra...@google.com> #51
02-17 14:02:25.255 D/WM-ConstraintTracker( 1608): NetworkStateTracker: initial state = [ Connected=false Validated=false Metered=false NotRoaming=false ]. This happens twice, like you described.
This is expected behavior. I think your disabling of flex has nothing to do with why the Worker was running when you had flex. I think the device does not have access to a reliable network as far as I can tell.
dr...@gmail.com <dr...@gmail.com> #52
And how do you explain why NOT specifying a flexInterval results in completely reliable periodic behaviour? While specifying a flexInterval makes it fall apart?
I don't follow your comment about expected behaviour... in the period of the logs, the device had no WiFi for at least two expected periodic runs, but didn't run because there was no network. That's expected behaviour, yes. What is not expected is that even when network was restored, the work did not run, ever again. That is when specifying a flexInterval.
But when not specifying a flexInterval, it all works smoothly... that doesn't make sense does it?
Am getting reports in now from said user... it's running like clockwork with the version that doesn't specify flexInterval when setting up the periodic work request.
ra...@google.com <ra...@google.com> #53
There is absolutely no difference in how constraints are handled.
Also, from the logs that you attached, it is clear that the device was not connected to a network. That's what I am basing this on. (NetworkStateTracker: initial state = [ Connected=false Validated=false Metered=false NotRoaming=false ]).
If you still think there might be something else going on, I think the best thing to do is to send us a sample app to reproduce this on our end.
dr...@gmail.com <dr...@gmail.com> #54
But there is a third one, later, that says "Connected=true":
02-17 14:40:50.345 D/WM-ConstraintTracker( 3798): NetworkStateTracker: initial state = [ Connected=true Validated=false Metered=false NotRoaming=true ]
And then immediately after:
02-17 14:40:50.345 D/WM-WorkConstraintsTrack( 3798): Constraints met for 635bfec6-81c9-499a-a9d2-5e273f07573c
So why did the work not run?
su...@google.com <su...@google.com> #55
dr...@gmail.com <dr...@gmail.com> #56
dr...@gmail.com <dr...@gmail.com> #57
02-20 10:28:12.735 D/WM-ConstraintProxy( 1227): onReceive : Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.cloud3squared.meteogram.devpro/androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy (has extras) }
02-20 10:28:14.770 D/WM-ConstraintTracker( 1227): NetworkStateTracker: initial state = [ Connected=true Validated=false Metered=false NotRoaming=true ]
02-20 10:28:14.815 D/WM-WorkConstraintsTrack( 1227): Constraints met for 5d210711-4c14-43cb-b6ea-2a6de910d76a
And yet, after this, the work doesn't run... there are no log entries from the startWork() function of the associated Worker.
Description
I think my issue is unrelated, hence this new bug report.
I am applying a network constrain to my PeriodicWorkRequest:
// ...
constraintsBuilder.setRequiredNetworkType(NetworkType.CONNECTED);
// ...
Constraints constraints = constraintsBuilder.build();
// ...
periodicWorkRequestBuilder.setConstraints(constraints);
// ...
PeriodicWorkRequest = periodicWorkRequestBuilder.build();
The idea is of course that the periodic work will run only if there is network. If there isn't, it will wait until there is, and then perform the work, and the subsequent periodic work will be scheduled after that.
This all works very well based on my own tests. But I have *one* user of an older Android 4.3 device where things break down (there may be others... but only one is reporting it... certainly it is not widespread).
From what I can determine, with a network constraint applied to the periodic work as above, if the periodic work is scheduled to run at time when there is no network, it appears on this user device that it does not run, as expected (because if if did, a "retry" work request would be scheduled by my code, which it isn't).
But it also appears that it NEVER RUNS AGAIN. In other words, the periodic work seems to be enqueued still, but just never runs again. It's as if the restoration of network doesn't kick it into running, and so it never does, ever again.
When this user runs an alternative version of the app where the network constraint is NOT applied to the periodic work (instead applying Constraints.NONE), the periodic work appears to survive the network downtime, and carry on ticking. It does attempt to run without network, which I can tell because a onetime "retry" work is scheduled, which surfaces later.
I have tried to reproduce this on an Android 4.3 emulator to no avail... seems to work as expected. So, this may not be enough information to go on.
It's almost as if CONNECTIVITY_CHANGE is not being received (I assume that WorkManager uses this signal)... or not acted on.
We don't need to add any special permissions or anything to make CONNECTIVITY_CHANGE work on all devices?