Solution (#6982): Lawnchair 16 crashes on older android versions (for example, andr#6984
Solution (#6982): Lawnchair 16 crashes on older android versions (for example, andr#6984TFGSUMIT wants to merge 1 commit into
Conversation
…droid versions (for example,
📝 WalkthroughWalkthroughAdds API-level fallback handling for ChangesViewTreeLifecycleOwner compatibility
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java (1)
5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueIdentical method duplicated in
ActivityManagerCompatVBaklava.Both
QuickstepCompatFactoryVBaklavaandActivityManagerCompatVBaklavahave the exact samegetViewTreeLifecycleOwnerimplementation. Consider extracting a shared utility or having one delegate to the other to avoid future divergence.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java` around lines 5 - 7, Remove the duplicate getViewTreeLifecycleOwner implementation between QuickstepCompatFactoryVBaklava and ActivityManagerCompatVBaklava by centralizing the logic in one shared utility or canonical class and delegating the other method to it. Preserve the existing AnimationHandler.getViewTreeLifecycleOwner(context) behavior and public method access.path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java (1)
10-20: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winCache the fallback instance instead of allocating per call.
The fallback
ViewTreeLifecycleOwneris stateless, yet a new anonymous instance is created on every invocation. Extract a static singleton to avoid unnecessary allocations on the pre-API-26 hot path.♻️ Proposed refactor: static singleton
+ private static final ViewTreeLifecycleOwner FALLBACK_OWNER = new ViewTreeLifecycleOwner() { + `@Override` + public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { + // No-op implementation + } + + `@Override` + public LifecycleOwner getLifecycleOwner() { + return null; + } + }; + public static ViewTreeLifecycleOwner getViewTreeLifecycleOwner(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { return context.getApplicationContext().getSystemService(ViewTreeLifecycleOwner.class); } else { - return new ViewTreeLifecycleOwner() { - `@Override` - public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { - // No-op implementation - } - - `@Override` - public LifecycleOwner getLifecycleOwner() { - return null; - } - }; + return FALLBACK_OWNER; } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java` around lines 10 - 20, Extract the anonymous fallback ViewTreeLifecycleOwner used by the surrounding method into a static singleton field, preserving its no-op setLifecycleOwner and null-returning getLifecycleOwner behavior. Update the fallback path to return this cached instance instead of allocating a new object on each invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java`:
- Around line 10-20: Update the fallback anonymous ViewTreeLifecycleOwner
implementation’s getLifecycleOwner() to return a non-null no-op LifecycleOwner
backed by a destroyed LifecycleRegistry, rather than null. Keep
setLifecycleOwner() as the existing no-op and ensure the fallback owner
satisfies LifecycleOwner callers on older Android versions.
- Around line 5-7: Update getViewTreeLifecycleOwner to accept a View or
tree-root parameter instead of Context, and retrieve the owner through
ViewTreeLifecycleOwner.get(view). Remove the API-level check and
getSystemService(ViewTreeLifecycleOwner.class) call, preserving the method’s
existing return behavior for the supplied view hierarchy.
In `@path/to/build.gradle`:
- Line 3: Update the dependency declaration in the Gradle configuration to
reference the included local project using the :androidx-lib project notation,
replacing the unresolved external androidx-lib:androidx-lib:1.0.0 coordinate.
---
Nitpick comments:
In
`@path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java`:
- Around line 10-20: Extract the anonymous fallback ViewTreeLifecycleOwner used
by the surrounding method into a static singleton field, preserving its no-op
setLifecycleOwner and null-returning getLifecycleOwner behavior. Update the
fallback path to return this cached instance instead of allocating a new object
on each invocation.
In
`@path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java`:
- Around line 5-7: Remove the duplicate getViewTreeLifecycleOwner implementation
between QuickstepCompatFactoryVBaklava and ActivityManagerCompatVBaklava by
centralizing the logic in one shared utility or canonical class and delegating
the other method to it. Preserve the existing
AnimationHandler.getViewTreeLifecycleOwner(context) behavior and public method
access.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0d7cfddf-d685-4504-9e51-7077cbd4b9d3
📒 Files selected for processing (4)
path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.javapath/to/build.gradlepath/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityManagerCompatVBaklava.javapath/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java
| public static ViewTreeLifecycleOwner getViewTreeLifecycleOwner(Context context) { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
| return context.getApplicationContext().getSystemService(ViewTreeLifecycleOwner.class); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if any custom getSystemService override handles ViewTreeLifecycleOwner
rg -n --type=java 'getSystemService.*ViewTreeLifecycleOwner|ViewTreeLifecycleOwner.*getSystemService' .Repository: LawnchairLauncher/lawnchair
Length of output: 351
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file and search for any custom service wiring or overrides
ast-grep outline path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java --view expanded || true
printf '\n--- getSystemService overrides ---\n'
rg -n --type=java 'override\s+.*getSystemService|getSystemService\(' path/to/androidx-lib src . \
| rg -n 'ViewTreeLifecycleOwner|ContextWrapper|Application|Service|Owner' || true
printf '\n--- ViewTreeLifecycleOwner usages ---\n'
rg -n --type=java 'ViewTreeLifecycleOwner' path/to/androidx-lib src . || trueRepository: LawnchairLauncher/lawnchair
Length of output: 45659
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the exact method and nearby imports for context
cat -n path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java | sed -n '1,120p'Repository: LawnchairLauncher/lawnchair
Length of output: 1199
🌐 Web query:
Android Context.getSystemService(Class) ViewTreeLifecycleOwner registered service docs
💡 Result:
There is no standard Android system service that can be retrieved via Context.getSystemService(Class) specifically to register or provide a ViewTreeLifecycleOwner [1][2]. The ViewTreeLifecycleOwner is not a system-level service, but rather a utility provided by the AndroidX Lifecycle library [3]. It acts as a bridge that allows views to locate and interact with the LifecycleOwner responsible for managing their view tree [3]. Key points regarding ViewTreeLifecycleOwner: 1. Purpose: It is used to associate a View hierarchy with a LifecycleOwner, which is essential for components like Jetpack Compose or other lifecycle-aware UI elements to function correctly [3][4]. 2. Not a System Service: You cannot register a custom "ViewTreeLifecycleOwner" service through the Android system's Context.getSystemService mechanism. System services are typically defined by the platform and retrieved by constant names or classes (e.g., WINDOW_SERVICE, ACCESSIBILITY_SERVICE) [1]. 3. Manual Association: When working in environments that do not automatically provide a LifecycleOwner (such as a Service, InputMethodService, or a custom Dialog), you must manually associate the LifecycleOwner with the view tree [4][5]. This is done using the static method: ViewTreeLifecycleOwner.set(view, lifecycleOwner) [3] If you encounter errors such as "ViewTreeLifecycleOwner not found," it is usually because the View hierarchy has not been explicitly linked to an object implementing LifecycleOwner (such as an Activity or a custom implementation of the interface) [4][6][5]. You must manually call ViewTreeLifecycleOwner.set on the root view of your component [3][5].
Citations:
- 1: https://developer.android.com/reference/android/content/Context
- 2: https://developer.android.com/reference/android/content/ContextWrapper
- 3: https://developer.android.com/reference/androidx/lifecycle/ViewTreeLifecycleOwner
- 4: https://stackoverflow.com/questions/65755763/inputmethodservice-with-jetpack-compose-composeview-causes-composed-into-the
- 5: https://stackoverflow.com/questions/75500480/viewtreelifecycleowner-not-found-from-dialog
- 6: https://stackoverflow.com/questions/79642715/viewtreelifecycleowner-not-found-from-composeview
Replace getSystemService(ViewTreeLifecycleOwner.class)
There is no platform ViewTreeLifecycleOwner system service, so this returns null on API 26+ and can NPE downstream. Pass a View (or tree root) and use ViewTreeLifecycleOwner.get(view) instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java`
around lines 5 - 7, Update getViewTreeLifecycleOwner to accept a View or
tree-root parameter instead of Context, and retrieve the owner through
ViewTreeLifecycleOwner.get(view). Remove the API-level check and
getSystemService(ViewTreeLifecycleOwner.class) call, preserving the method’s
existing return behavior for the supplied view hierarchy.
| return new ViewTreeLifecycleOwner() { | ||
| @Override | ||
| public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { | ||
| // No-op implementation | ||
| } | ||
|
|
||
| @Override | ||
| public LifecycleOwner getLifecycleOwner() { | ||
| return null; | ||
| } | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Fallback getLifecycleOwner() returning null risks NPEs on older Android.
The fallback's getLifecycleOwner() returns null. If any downstream caller invokes getLifecycleOwner() on the result and uses it without a null check, this will crash on the exact older Android versions this PR aims to fix. Consider returning a no-op LifecycleOwner (e.g., one backed by LifecycleRegistry.create() in a DESTROYED state) instead of null, or ensure all callers null-check the result.
🛡️ Proposed fix: return a no-op LifecycleOwner instead of null
return new ViewTreeLifecycleOwner() {
`@Override`
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
// No-op implementation
}
`@Override`
public LifecycleOwner getLifecycleOwner() {
- return null;
+ return new LifecycleOwner() {
+ `@Override`
+ public Lifecycle getLifecycle() {
+ return LifecycleRegistry.create(this);
+ }
+ };
}
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java`
around lines 10 - 20, Update the fallback anonymous ViewTreeLifecycleOwner
implementation’s getLifecycleOwner() to return a non-null no-op LifecycleOwner
backed by a destroyed LifecycleRegistry, rather than null. Keep
setLifecycleOwner() as the existing no-op and ensure the fallback owner
satisfies LifecycleOwner callers on older Android versions.
| @@ -0,0 +1,4 @@ | |||
| // Update the androidx-lib dependency to include the fallback implementation for ViewTreeLifecycleOwner | |||
| dependencies { | |||
| implementation 'androidx-lib:androidx-lib:1.0.0' | |||
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if androidx-lib is declared as a local module in settings.gradle
rg -n 'androidx-lib' settings.gradle settings.gradle.kts build.gradle build.gradle.ktsRepository: LawnchairLauncher/lawnchair
Length of output: 326
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== settings.gradle excerpt ==\n'
sed -n '100,125p' settings.gradle
printf '\n== occurrences of androidx-lib and related dependency declarations ==\n'
rg -n --hidden --glob '!**/.git/**' 'androidx-lib|project\(\x27:androidx-lib\x27\)|implementation .*androidx-lib:androidx-lib:1\.0\.0|group\s*=|artifactId|archivesBaseName' \
settings.gradle build.gradle gradle.properties **/build.gradle **/build.gradle.kts **/settings.gradle **/settings.gradle.kts 2>/dev/null || true
printf '\n== files for the included module ==\n'
fd -a '^build\.gradle(\.kts)?$|^settings\.gradle(\.kts)?$' . | rg '/androidx-lib/'Repository: LawnchairLauncher/lawnchair
Length of output: 837
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== module build.gradle ==\n'
sed -n '1,220p' androidx-lib/build.gradle
printf '\n== root build.gradle repository and resolution config ==\n'
sed -n '1,220p' build.gradle
printf '\n== search for group/version/artifact mapping or dependency substitution ==\n'
rg -n --hidden --glob '!**/.git/**' 'group\s*=|version\s*=|archivesBaseName|artifactId|dependencySubstitution|includeBuild|resolutionStrategy|androidx-lib:androidx-lib:1\.0\.0|project\(\x27:androidx-lib\x27\)' \
androidx-lib build.gradle settings.gradle gradle.properties 2>/dev/null || trueRepository: LawnchairLauncher/lawnchair
Length of output: 8296
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== publication / substitution hints in androidx-lib and root ==\n'
rg -n --hidden --glob '!**/.git/**' \
'maven-publish|publishing\s*\{|publication|artifactId|group\s*=|version\s*=|dependencySubstitution|substitute\(|project\(\x27:androidx-lib\x27\)|androidx-lib:androidx-lib:1\.0\.0' \
androidx-lib build.gradle settings.gradle gradle.properties gradle/libs.versions.toml 2>/dev/null || true
printf '\n== complete androidx-lib build file ==\n'
cat -n androidx-lib/build.gradleRepository: LawnchairLauncher/lawnchair
Length of output: 4461
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== settings.gradle (top section) ==\n'
sed -n '1,80p' settings.gradle
printf '\n== root repository / dependency resolution config ==\n'
rg -n --hidden --glob '!**/.git/**' 'dependencyResolutionManagement|repositories\s*\{|maven\s*\{|flatDir\s*\{|resolutionStrategy|dependencySubstitution|substitute\(|includeBuild|project\(\x27:androidx-lib\x27\)|androidx-lib:androidx-lib:1\.0\.0' \
settings.gradle build.gradle gradle.properties gradle/libs.versions.toml 2>/dev/null || trueRepository: LawnchairLauncher/lawnchair
Length of output: 2835
Use the included :androidx-lib project here. androidx-lib:androidx-lib:1.0.0 isn’t mapped to any publication or substitution rule in this repo, so it won’t resolve to the local module as written.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@path/to/build.gradle` at line 3, Update the dependency declaration in the
Gradle configuration to reference the included local project using the
:androidx-lib project notation, replacing the unresolved external
androidx-lib:androidx-lib:1.0.0 coordinate.
This pull request resolves the issue of Lawnchair 16 crashing on older Android versions by adding a fallback implementation for
ViewTreeLifecycleOwnerinAnimationHandler.java. The changes made include:AnimationHandler.javato include a fallback implementation forViewTreeLifecycleOwnerin older Android versionsActivityManagerCompatVBaklava.javaandQuickstepCompatFactoryVBaklava.javato use the fallback implementationbuild.gradleto include the fallback implementation in theandroidx-libdependencyTo test this pull request, please follow these instructions:
build.gradlefile to include the fallback implementation in theandroidx-libdependency.Summary by CodeRabbit
New Features
Bug Fixes