-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reworked savepoints to support recovering from older form versions #5951
Conversation
79a61c9
to
4147b0b
Compare
4147b0b
to
75578bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really liking the switch to a DB backed repo for save points.
Some high level thoughts from my initial look through (plus one inline):
- It looks like we load the save point in
FormUriActivity
and then again inFormLoaderTask
. Could we just pass some indication to use the save point fromFormUriActivity
toFormFillingActivity
(theSavePoint
itself is small enough to parcelize for example) to avoid loading it twice? - It looks to me like bulk finalization will still use the old way of loading save points using
FormEntryUseCases.getSavePoint()
. I think it should be easy enough to fix that by either usingSavePointFinder
there instead or (and I think in a way that I prefer) replaceFormEntryUseCases.getSavePoint()
's implementation withSavePointFinder#getSavePoint
and removeSavePointFinder
.
collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt
Outdated
Show resolved
Hide resolved
This was my initial plan too but it would make dealing with savepoints more complex. Imagine a scenario where:
In such a scenario the savepoint object that represents a non-existing savepoint will be passed to the activity and used. |
7937fe5
to
e1e9493
Compare
Good catch, I've just fixed that but just with |
Great point! I think we're really hitting a point where |
I agree it's been quite common recently to run into problems cased by the fact that we use two activities to start form filling. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've had another look through and added some comments. There's probably one more proper pass I need to do to review SavePointFinder
and the repositories, but I've managed to get a good sense of the structure of everything now.
...pp/src/androidTest/java/org/odk/collect/android/support/pages/SavepointRecoveryDialogPage.kt
Outdated
Show resolved
Hide resolved
collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt
Outdated
Show resolved
Hide resolved
...pp/src/main/java/org/odk/collect/android/database/savepoints/DatabaseSavepointsRepository.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/utilities/SavepointsRepositoryProvider.kt
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/savepoints/SavepointTask.kt
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt
Outdated
Show resolved
Hide resolved
...p/src/main/java/org/odk/collect/android/database/savepoints/DatabaseSavepointObjectMapper.kt
Outdated
Show resolved
Hide resolved
...pp/src/main/java/org/odk/collect/android/database/savepoints/DatabaseSavepointsRepository.kt
Outdated
Show resolved
Hide resolved
...pp/src/main/java/org/odk/collect/android/database/savepoints/DatabaseSavepointsRepository.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/tasks/SaveFormToDisk.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt
Outdated
Show resolved
Hide resolved
collect_app/src/test/java/org/odk/collect/android/external/FormUriActivityTest.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/savepoints/SavepointFinder.kt
Outdated
Show resolved
Hide resolved
collect_app/src/test/java/org/odk/collect/android/savepoints/SavepointFinderTest.kt
Outdated
Show resolved
Hide resolved
dbf1c63
to
e7045fd
Compare
Tested with Success! Verified on devices with Androids: 10 Verified cases:
|
Tested with Success! Verified on device with Android 11 |
Tested with Success! Verified on device with Android 12,13 |
Closes #4879
Closes #3494
Why is this the best possible solution? Were any other approaches considered?
The original implementation of handling savepoints was based on files saved in the cache directory (a savepoint file is a normal XML file just like we store saved forms and send them to a server). Let me explain the process of creating and then identifying such a savepoint. For the purpose of this example let's use the demo form called
All widgets
.All widgets
in this case) we create an empty instance folder that consists of the form name and the date of creation and might look likeAll widgets_2024-02-29_09-27-51
. This value is saved to theFormController
as the instance name.All widgets_2024-02-29_09-27-51.xml.save
.All widgets
again we try to figure out if there is a savepoint for this form and if it exists we want to load it. The problem is that we don't have enough data to correctly identify a savepoint that belongs to our form so we try to do that in a pretty cumbersome way:instances.db
because we were filling a blank form so lost that date of creation that was used to create the instance name and was a second part of the savepoint file name.All widgets
so we browse the cache directory looking for a file that has a name starting withAll widgets
and ending with.xml.save
completely ignoring the part of the name that was built using the date of creation.All widgets_2024-02-29_09-27-51.xml.save
matches so we take it and based on this file name we try to find the instance directory (that at this moment is empty but we want to continue using it) that was saved when we started the form for the first time (before creating the savepoint), what I have described in the first point.We have only supported recovering savepoints for the newest form versions so it worked. However, it was cumbersome enough. Now as we want to support recovering savepoints from older versions too, looking for savepoint files that belong to older versions of a form that we are opening in a similar way would be a spaghetti code.
Taking all of that into account I decided we need a database to keep all the needed information in a clear and structured way. Thanks to this database based on our form id and version we can easily figure out if there is a savepoint for our form or not. If it exists we can read its full name instead of looping the whole cache dir and try to find the file we want based on its prefix and suffix.
This pull request does not contain any mechanism for data migration. This will be implemented separately #5997.
How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?
We need to test opening blank and saved forms with and without savepoints to make sure everything works fine. There are a lot of combinations if there are multiple versions of the same form so it will be fun. We can chat about it as well so that I can tell you more about this issue.
Do we need any specific form for testing your changes? If so, please attach one.
No, it can be any form.
Does this change require updates to documentation? If so, please file an issue here and include the link below.
Yes getodk/docs#1749
Before submitting this PR, please make sure you have:
./gradlew connectedAndroidTest
(or./gradlew testLab
) and confirmed all checks still pass