Skip to content

Conversation

@Kota-Jagadeesh
Copy link
Collaborator

Description (required)

Fixes #6433

Whatt changes did you make and why?

This PR fixes a critical crash in the upload progress screen that occurs when the device is rotated or resized while uploads are pending, and the user quickly interacts with the "Pause" or "Cancel" menu item.

The fix addresses both the root cause (Activity restart) and the specific crash location (Uninitialized Presenter):

1. Prevent Activity Restart (AndroidManifest.xml)

The UploadProgressActivity was restarting during configuration changes (like rotation or split-screen resize). This restart cycle caused the app to call fragment methods before Dagger could re-inject the dependencies.

  • Added thee smallestScreenSize and screenLayout to the android:configChanges attribute for UploadProgressActivity. This prevents the activity from being destroyed on resize events, thereby preserving the fragment's state and preventing the presenter from becoming uninitialized.

2. Defensive Presenter Access (PendingUploadsFragment.kt)

Even with the Manifest change, fast user interaction or edge cases in the activity lifecycle could still lead to a crash.

  • Added defensive checks using if (::pendingUploadsPresenter.isInitialized) to restartUploads(), pauseUploads(), and deleteUploads(). This ensures that the presenter is ready before any businesss logic methods are called, robustly preventing the kotlin.UninitializedPropertyAccessException crash which was used to ocurr.

Tests performed (required)

Tested ProdDebug on Redmi note 13 pro 5g with API level 35.

  1. Rotation/Resize Test: Verified that rotating the device (portrait to landscape and vice versa) while uploads are pending no longer causes a crash or state loss happens.
  2. Crash Reproduction Test: Performed the original steps: Start uploads, rotate device, and quickly tap "Pause uploads." The app correctly pauses the uploads without crashing.

@RitikaPahwa4444
Copy link
Collaborator

RitikaPahwa4444 commented Nov 25, 2025

@Kota-Jagadeesh thanks for the PR. Would you mind elaborating how this approach is different from the PR you closed (#6532) and why you suggest it over the one we discussed before?

@Kota-Jagadeesh
Copy link
Collaborator Author

@Kota-Jagadeesh thanks for the PR. Would you mind elaborating how this approach is different from the PR you closed (#6532) and why you suggest it over the one we discussed before?

Thanks for following up on PR #6532.

  • To fix this i needed to refer a lot of documentations to know the exact and the correct way to fix it, and proposed this changes

That previous PR was a architecturall improovment to the UploadProgressActivity, but it failed to adress the root cuase of the crash and the specific lateinit problem simultaneously.

Here is the elaboraton on why the current PR is different and why I suggest it over the one we previously discused:

The Core Problem and the Two Approaches

The crash, kotlin.UninitializedPropertyAccessException: lateinit property pendingUploadsPresenter has not been initialized, is caused by the two compounding factorrs:

  1. Activity Restart: rotation/resize restarts UploadProgressActivity.
  2. timing error: the activity's menu handler tries to call the method on the fragment befoure Dagger has re-injected the presenter. (AFAIK)
Aspect Closed PR (#6532) Current PR (Preferred)
Manifest Change None. Activity still restarted. Critical: Added the android:configChanges to to prevent the restart.
Fragment Change No chnages to PendingUploadsFragment.kt. Critical: Added if (::pendingUploadsPresenter.isInitialized) checks in the fragment.
Activity Change (Lookup) Used supportFragmentManager.find helpers. Goal: Robust fragment lookupp. No change needed. here

2. Why i feel the current Approach is Better

Eliminates the Root Cause (Manifest Change)

The crash happens becuz the UploadProgressActivity is being destroyed and recreated on rotation. since this restart causes a brief window where the fragment's UI is alive, but Dagger hasn't re-injected the presenter:

  • Here byy adding the android:configChanges="orientation|screenSize|keyboard|smallestScreenSize|screenLayout" to UploadProgressActivity in the Manifest, we tell theeAndroid not to restart the Activity on rotation or resize. It simply calls onConfigurationChanged().
  • Result: The presenter remains initialized, and the timing issue that causes the crash is eliminated. IMO, this is the clean, recommended Android fix for lifecycle stability issues in an Activity.

Maintains Best Practice (Fragment Code)

The previous approach in PR #6532 focused on making the Activity's fragment lookup safe, but it left the fragment internal lateinit property vulnerable.

  • The current approach keeps the lateinit var, which signals that the presenter must be injected (Dagger's contract). The added if (::pendingUploadsPresenter.isInitialized) checks are a small, highly localized safety measure to handle the rare moment where an external call (like a the menu click from the Activity) happens before the injection is fully complete.

In a shorrt summary summary, the previous PR was a workaround for the symptom by making the fragment lookups safe. Thiss current PR is a proper fix (IMO) that addresses the root cause (Activity restart in the Manifest) and adds the robust defensive programmingg in the fragment.

This ensures stability, better good performance (no full Activity restart), and adherence to the Dagger/MVP contract by keeping the presenteer non-nullable.

Thanks : )

@RitikaPahwa4444
Copy link
Collaborator

it failed to adress the root cuase of the crash and the specific lateinit problem simultaneously

As per my understanding, we did figure out and address the root cause. Could you please elaborate what was missing there?

timing error: the activity's menu handler tries to call the method on the fragment befoure Dagger has re-injected the presenter. (AFAIK)

It wasn't a timing error as it was always reproducible, even after waiting for a long time.

I would suggest reading https://developer.android.com/guide/topics/resources/runtime-changes for understanding configuration changes in depth, otherwise it's a rabbit hole.

@Kota-Jagadeesh
Copy link
Collaborator Author

thank you for the detailed feedback and for the resource link - I definitely agree that understanding configuration chnages in depth is key to avoiding rabbit holes!

I need to apologize for my previous phrasing "i understood i didnt phrase it properly" ; you are entirely correct. Calling it a "timing error" was misleeding. It should better be described as a Deterministic Lifecycle Ordering Issue that leads to this crash when an external call (from the Activity's menu handler) interrupts the fragments dependency re-injection sequence. Since the Activity always restarts, the crash is always reproducible.

Here is the breakdown of what PR #6532 fixed versus what was still missing to fix the lateinit exception:

1. Two "Root Causes" to Address

The crash has two distinct dependency issues, and PR #6532 only fixed one:

Issue Location Status after PR #6532
Activity Root Cause AndroidManifest.xml UNFIXED. The Activity continued to restart on rotation/resize.
Fragment Instability UploadProgressActivity.kt (Fragment Field) FIXED. Fragment lookups in the Activity menu handlers became safe using supportFragmentManager.find helpers.
Dagger Crash Point PendingUploadsFragment.kt (The lateinit property) UNFIXED. The UninitializedPropertyAccessException was still possible if the Activity restarted and the menu handler fired before Dagger ran in the Fragment's re-creation cycle.

What was missing was addressing the Activity restart itself (the true root cause of the lifecycle problem) and adding a safety check inside the Fragment for the Dagger contract.

2. Why the Current PR is the Complete Fix

The current PR is superior because it applies the proper dual fix:

A. Eliminating the Activity Restart (Manifest Fix)

  • Action: Added android:configChanges to UploadProgressActivity.
  • Result: This is the cleanest way to preserve the Activity and Fragment state. We eliminate the primary lifecycle event (destroy ->create) that causes the presenter to become uninitialized. This is the most robust fix and aligns with the best practice of handling configuration chnages internally when possible.

B. Defending the Dagger Contract (Fragment Fix)

  • Action: Added if (::pendingUploadsPresenter.isInitialized) checks in PendingUploadsFragment.kt.
  • Result: The previous PR was correct to address the Activity's fragment lookups. However, this check is necessary to prevent the specific lateinit crash on the rare occasion (or deterministic occasion, in this case) that the menu call happens before the fragment's Dagger injection is complete, even with the manifest fix in place.

In short, PR #6532 made the Activity's menu code robust against null fragments, but it didn't solve the Fragment's presenter initialization crash. The current PR solves the latter by stopping the Activity restart and adding the necessary Dagger safety check.

This gives us maximum stability and prevents the consistently reproducible crash.

@RitikaPahwa4444
Copy link
Collaborator

UNFIXED. The Activity continued to restart on rotation/resize.

It is supposed to get recreated and there's nothing to fix.

UNFIXED. The UninitializedPropertyAccessException was still possible if the Activity restarted and the menu handler fired before Dagger ran in the Fragment's re-creation cycle.

Did those logs not prove otherwise? Could you please share some data points, steps to reproduce or links to issue trackers or stack overflow that suggest this is a known issue?

I would again suggest reading the official docs and coming up with the cons of the approach you followed in this PR; there are always some tradeoffs in software engineering. Do share what we're gaining and at what cost. Thanks! :)

@Kota-Jagadeesh
Copy link
Collaborator Author

@RitikaPahwa4444 Thank you for the detailed feedback on the above commentt. This is a very important point about the architecture, and I thank you guiding me to the official documentation.

I believe the confusion over the "root cause" stems from needing to solve two different problems:

1. Why the Crash is a "Fixable Timing Issue"

You are absolutely correct that the Activity is supposed to get recreated on configuration changes by default. However, this "recreation" (destroy --> create) is precisely what causes the crash.

  • Default Behavior: Activity recreation clears out all state, forcing Dagger to re-inject the lateinit presenter after the new fragment instance is created.
  • The Crash: When the menu handler fires immediately, it hits the lateinit property before Dagger's re-injection task finishes.

By using the android:configChanges approach, we are choosing the documented way to "Restrict activity recreation" because we prioritize the user experience of preserving the form state. this isn't just an optimization; it's the necessary mechanism to prevent this specific, deterministic crash.

2. What We Gain

We Gain (Benefit)
Stability (P0): Eliminates the lateinit crash (our primary bug) by preventing the Activity from restarting and losing the Dagger contract.
Performance/UX: Provides a seamless user experience (UX) during resizing and multi-window use by preserving the form state.
Robustness: The added ::isInitialized check provides a safety net against other potential lifecycle issues or process death.

In short, we are gaining stability and UX at the cost of giving up automatic resource reloading, which is the necessary and known tradeoff when using g android:configChanges. thiss tradeoff is worth it for a critical, multi-step workflow like upload.

please correct me if i am wrong, Thank you!!

@github-actions
Copy link

✅ Generated APK variants!

@RitikaPahwa4444
Copy link
Collaborator

The Crash: When the menu handler fires immediately, it hits the lateinit property before Dagger's re-injection task finishes.

This isn't happening as it's not a timing problem, it also occurs long after the configuration change. Could you share the specific data points behind this? You’ve raised it a few times, and I want to align on facts rather than assumptions.

we are choosing the documented way to "Restrict activity recreation" because we prioritize the user experience of preserving the form state. this isn't just an optimization; it's the necessary mechanism to prevent this specific, deterministic crash

There are caveats - we can't always prevent it on newer devices. Kindly read the documentation for more details.

Robustness: The added ::isInitialized check provides a safety net against other potential lifecycle issues or process death.

Let's keep the fix limited to the scope of the issue instead of fixing any "potential" issues we haven't been able to reproduce so far.

Also, I'm still confused why the other PR had been termed as a "workaround" while this one itself has caveats as we're just trying to stop activity recreation, which isn't ideal. Could you please share what kind of configuration changes, apart from screen rotation, have you tested for while working on this PR? This change is expected to impact all of them, unlike the previous PR. Also, this question from my previous comment is still unresolved:

Did those logs not prove otherwise? Could you please share some data points, steps to reproduce or links to issue trackers or stack overflow that suggest this is a known issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lateinit property pendingUploadsPresenter has not been initialized

2 participants