Skip to content

Solution (#6982): Lawnchair 16 crashes on older android versions (for example, andr#6984

Open
TFGSUMIT wants to merge 1 commit into
LawnchairLauncher:16-devfrom
TFGSUMIT:fix/issue-6982
Open

Solution (#6982): Lawnchair 16 crashes on older android versions (for example, andr#6984
TFGSUMIT wants to merge 1 commit into
LawnchairLauncher:16-devfrom
TFGSUMIT:fix/issue-6982

Conversation

@TFGSUMIT

@TFGSUMIT TFGSUMIT commented Jul 11, 2026

Copy link
Copy Markdown

This pull request resolves the issue of Lawnchair 16 crashing on older Android versions by adding a fallback implementation for ViewTreeLifecycleOwner in AnimationHandler.java. The changes made include:

  • Modifying AnimationHandler.java to include a fallback implementation for ViewTreeLifecycleOwner in older Android versions
  • Updating ActivityManagerCompatVBaklava.java and QuickstepCompatFactoryVBaklava.java to use the fallback implementation
  • Updating build.gradle to include the fallback implementation in the androidx-lib dependency

To test this pull request, please follow these instructions:

  1. Clone the repository and update the build.gradle file to include the fallback implementation in the androidx-lib dependency.
  2. Build and run the app on an Android 8 device or emulator.
  3. Launch the app and verify that it does not crash when launching.
  4. Repeat the test on other older Android versions to ensure that the app does not crash when launching.

Summary by CodeRabbit

  • New Features

    • Added improved lifecycle-owner compatibility across supported Android versions.
    • Added a fallback for older Android releases where the standard lifecycle-owner API is unavailable.
  • Bug Fixes

    • Improved compatibility for activity and launcher integrations on older Android versions.
    • Reduced potential failures when retrieving lifecycle information across different platform versions.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds API-level fallback handling for ViewTreeLifecycleOwner, includes the supporting androidx-lib dependency, and delegates two compatibility helpers to the new AnimationHandler resolver.

Changes

ViewTreeLifecycleOwner compatibility

Layer / File(s) Summary
Fallback resolver and dependency
path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java, path/to/build.gradle
Adds API 26+ system resolution and a pre-26 no-op fallback implementation, with the supporting androidx-lib dependency.
Compatibility helper delegation
path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/*
Adds or updates compatibility helpers to delegate lifecycle-owner resolution to AnimationHandler.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels: bug-fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the main fix, but it is truncated and not fully complete. Use a complete, concise title that clearly states the main change, such as adding a ViewTreeLifecycleOwner fallback for older Android versions.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description summarizes the crash fix and includes testing steps, but it omits the template's Reasoning and Type of change sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request has been flagged as potential spam (gibberish) by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 value

Identical method duplicated in ActivityManagerCompatVBaklava.

Both QuickstepCompatFactoryVBaklava and ActivityManagerCompatVBaklava have the exact same getViewTreeLifecycleOwner implementation. 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 win

Cache the fallback instance instead of allocating per call.

The fallback ViewTreeLifecycleOwner is 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

📥 Commits

Reviewing files that changed from the base of the PR and between f6256e2 and cd17bcf.

📒 Files selected for processing (4)
  • path/to/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java
  • path/to/build.gradle
  • path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityManagerCompatVBaklava.java
  • path/to/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java

Comment on lines +5 to +7
public static ViewTreeLifecycleOwner getViewTreeLifecycleOwner(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return context.getApplicationContext().getSystemService(ViewTreeLifecycleOwner.class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 . || true

Repository: 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:


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.

Comment on lines +10 to +20
return new ViewTreeLifecycleOwner() {
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
// No-op implementation
}

@Override
public LifecycleOwner getLifecycleOwner() {
return null;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment thread path/to/build.gradle
@@ -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'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.kts

Repository: 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 || true

Repository: 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.gradle

Repository: 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 || true

Repository: 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.

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.

1 participant