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
Fixed TransactionTooLargeException in FormDownloadList activity #2799
Conversation
2b247b3
to
f33ec84
Compare
Since this is a high risk change, I think we should save this until the next release. What do you think, @grzesiek2010? |
Ok, we can wait. |
This error is the worst 💣! Ran into it on a couple of apps. +1 for using Just thinking ahead: as you point out @grzesiek2010 "The activity might be destroyed if we close the app using home button and our device has not much memory". As you say using |
It's not a problem, we just load forms again what is ok and it's maybe even better: It's a different thing but:
You seem experienced in this area so maybe you tell us what your opinion is about: I was thinking about using it in Collect but haven't even created an issue so far. |
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.
As far as I can see there aren't any problems with the migration from the Bundle
to the ViewModel
in terms of storing state for the Activity. However, a lot of individual states have been migrated so I think we need to make sure this runs through QA. I think in the future it would be best to have changes like this covered by tests of the original implementation so that we know the refactor hasn't broken anything. We can use Robolectric to test lifecycle based behavior like this but it might have been easier to do with Espresso and "Don't keep activities". Happy to chat through this at some point. I saw this was pretty common in #2794 though so it probably makes sense to just move forward with manual testing to get this in.
It's not a problem, we just load forms again
Ah right if that's the case then the only problem I can see is the user being offline when the Activity reloads. If currently the app would explode though this would certainly be an improvement!
On icepick and android-state: I've definitely seen them before. Generally when I've encountered this exception before it's because the arguments being passed to an Activity or Fragment are too large rather than a deliberate onSaveInstanceState()
so the fix has been different. I do think rearchitecting toViewModel
makes more sense in general than those frameworks as it's now a main line piece of Android's Jetpack framework. This means it's going to get first class support, be more in sync with other framework changes and also has great interplay with other pieces such as LiveData
(which could be nice in this Activity as an aside). I'd also tend towards avoiding stateful Activity or Fragment objects as much as possible. If we really need to be reloading "session" state I think it'd be cleaner to move that into SharedPreferences
or Room
and have the ViewModel
pattern abstract/decouple it from our UI and lifecycle code.
…ialog twice after destroying the activity
f33ec84
to
89e6044
Compare
Tested with success I noticed two differences in behavior and I talked about them with @grzesiek2010
Tested on Android: 4.2, 4.4, 5.1, 6.0, 7.0, 8.1 Tested cases:
@opendatakit-bot unlabel "needs testing" |
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.
A few minor PR comments, plus the below feedback (always with "do not keep activities" turned on):
-
When I cancel the loading dialog, leave the activity, and come back, it tries to load the forms again. Shouldn't it respect
loadingCanceled
? -
When I check some forms, leave, and come back, the forms become un-checked (note: this doesn't happen on the Send Finalized activity for example, that one remembers the checked items).
@@ -163,7 +164,7 @@ public void setUrl(String url) { | |||
} | |||
|
|||
public String[] getFormIdsToDownload() { | |||
return formIdsToDownload; | |||
return Arrays.copyOf(formIdsToDownload, formIdsToDownload.length); |
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.
Can we make this an ArrayList like the others?
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.
The code related to external apps is relatively new and a bit fishy to me. I don't want to touch it here because it would require testing again. There is still a pr on the collectTester's side https://github.com/grzesiek2010/collectTester/pulls I'll take a look at it then.
formList.add(item); | ||
} | ||
|
||
public void addFormList(int index, HashMap<String, String> item) { |
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.
Let's rename to addForm
(it's not adding a new list, it's adding a new item to the existing list)
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.
Fixed.
private boolean shouldExit; | ||
private boolean loadingCanceled; | ||
// Variables for the external app intent call | ||
private boolean isDownloadOnlyMode; |
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.
Can we improve this comment? I know you just copied what was there before, but it's unclear what it actually means (and it should probably be JavaDoc).
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.
Fixed.
} | ||
|
||
public HashMap<String, Boolean> getFormResult() { | ||
return formResult; |
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.
Let's pluralize for clarity. Then the methods would be getFormResults
(plural, whole map) and putFormResult
(singular, single item).
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.
Fixed.
@cooperka
it's because the Once you accept the pr I'll merge it. |
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 understand now, thanks for clarifying. I agree that losing state on destroy is probably fine here because it's a fairly ephemeral screen -- you use it in a single session to download some forms, so long-lived state isn't that important. LGTM.
Closes #2794
Explanation
TransactionTooLargeException is thrown when an Activity is in the process of stopping, that means that the Activity was trying to send its saved state Bundles to the system OS for safe keeping for restoration later (after a config change or process death) but that one or more of the Bundles it sent were too large. There is a maximum limit of about 1MB for all such transactions occurring at once and that limit can be reached even if no single Bundle exceeds that limit
The stacktrace I attached to the issue doesn't tell us which activity cause the issue. But after reviewing values we safe in our activities and investigating logs from firebase I'm sure it must be the FormDownloadList activity.
What has been done to verify that this works as intended?
I performed manual testing of the
FormDownloadList
activity.Why is this the best possible solution? Were any other approaches considered?
First I considered using frankiesardo/icepick + livefront/bridge libraries. It would allow us to fix the issue just in a few lines since the second mentioned library has been created to fix that problem (TransactionTooLargeException). Unfortunately, we would need to add two new dependencies. frankiesardo/icepick is really popular and we would consider using that library to reduce boilerplate code but livefront/bridge has only 100 stars and might be not well tested.
I decided not to take a risk using two new dependencies. That's why I used ViewModel class.
https://developer.android.com/topic/libraries/architecture/viewmodel
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
That means the class keeps our data until the activity is destroyed. If activity was destroyed we need to load available forms again - in this case so using ViewModel class seems reasonable. Activity is not destroyed once we rotate our device so in this case, nothing is going to change. The activity might be destroyed if we close the app using home button and our device has not much memory.
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?
It introduces a lot of refactoring so the whole functionality that the
FormDownloadList
activity provides needs to be carefully tested. Please pay attention to dialogs that activity might display.Everything should work as before apart from the behavior after destroying the activity. If activity was destroyed we need to load available forms again.
Using
Don't keep activities
option would be helpful here.Do we need any specific form for testing your changes? If so, please attach one.
No.
Does this change require updates to documentation? If so, please file an issue here and include the link below.
No.
Before submitting this PR, please make sure you have:
./gradlew checkAll
and confirmed all checks still pass OR confirm CircleCI build passes and run./gradlew connectedDebugAndroidTest
locally.