Skip to content

fix usage share crash null GlobalKey context before tab renders#7509

Merged
kodjima33 merged 1 commit into
mainfrom
fix/usage-share-null-context
May 28, 2026
Merged

fix usage share crash null GlobalKey context before tab renders#7509
kodjima33 merged 1 commit into
mainfrom
fix/usage-share-null-context

Conversation

@krushnarout
Copy link
Copy Markdown
Member

Crash

_UsagePageState._shareUsage

Error: FlutterError - Null check operator used on a null value
package:omi/pages/settings/usage_page.dart:172

Crashlytics: https://console.firebase.google.com/u/0/project/based-hardware/crashlytics/app/ios:com.friend-app-with-wearable.ios12/issues/85cdd34aeecb1d6a0b50cdc14083ef78?time=7d&types=crash&sessionEventKey=16956422ab7248b6940d706ae0b6eb02_2222284095727765224

Logs:

Fatal Exception: FlutterError
0  ???  0x0 _UsagePageState._shareUsage + 68 (usage_page.dart:68)
1  ???  0x0 _InkResponseState.handleTap + 1204 (ink_well.dart:1204)
2  ???  0x0 GestureRecognizer.invokeCallback + 345 (recognizer.dart:345)
3  ???  0x0 TapGestureRecognizer.handleTapUp + 758 (tap.dart:758)
4  ???  0x0 BaseTapGestureRecognizer._checkUp + 383 (tap.dart:383)
5  ???  0x0 BaseTapGestureRecognizer.handlePrimaryPointer + 314 (tap.tap:314)
6  ???  0x0 PrimaryPointerGestureRecognizer.handleEvent + 721 (recognizer.dart:721)
7  ???  0x0 PointerRouter._dispatch + 97 (pointer_router.dart:97)
8  ???  0x0 PointerRouter._dispatchEventToRoutes.<fn> + 142 (pointer_router.dart:142)
9  ???  0x0 _LinkedHashMapMixin.forEach (dart:_compact_hash)
10 ???  0x0 PointerRouter._dispatchEventToRoutes + 140 (pointer_router.dart:140)
11 ???  0x0 PointerRouter.route + 130 (pointer_router.dart:130)
12 ???  0x0 GestureBinding.handleEvent + 528 (binding.dart:528)
13 ???  0x0 GestureBinding.dispatchEvent + 498 (binding.dart:498)
14 ???  0x0 RendererBinding.dispatchEvent + 473 (binding.dart:473)
15 ???  0x0 GestureBinding._handlePointerEventImmediately + 437 (binding.dart:437)
16 ???  0x0 GestureBinding.handlePointerEvent + 394 (binding.dart:394)
17 ???  0x0 GestureBinding._flushPointerEventQueue + 341 (binding.dart:341)
18 ???  0x0 GestureBinding._handlePointerDataPacket + 308 (binding.dart:308)

Fix

_screenshotKeys[index].currentContext! crashed when the tab's widget hadn't rendered yet — the GlobalKey has no context until the tab is visited. Replaced the force-unwrap with a null guard that returns early, and added a !mounted check after the first await to bail if the widget is disposed during image capture.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 27, 2026

Greptile Summary

Fixes a production crash in _shareUsage where GlobalKey.currentContext was null when the user tapped the share button on a tab that hadn't been rendered yet. The force-unwrap (!) is replaced with a null guard that returns early, and a !mounted check is added after the first async gap.

  • The null guard on captureContext (line 68) and the early return correctly prevent the fatal FlutterError reported in Crashlytics.
  • A !mounted check is added after boundary.toImage() but not after the several subsequent await calls; context is captured upfront so no widget-tree crash results, but the file write and share sheet can still fire from a disposed widget.

Confidence Score: 4/5

Safe to merge — the change is narrowly scoped to the crash path and does not alter any existing behavior beyond preventing the null dereference.

The root cause of the Crashlytics crash is correctly addressed. The only remaining observations are that findRenderObject() still uses a forced cast (rather than a null-safe one) and that !mounted guards are absent from the later async gaps — neither of which can cause the original crash or introduce new widget-tree errors given that context is captured before the first await.

No files require special attention; all changes are contained within app/lib/pages/settings/usage_page.dart.

Important Files Changed

Filename Overview
app/lib/pages/settings/usage_page.dart Crash fix: null-guards GlobalKey.currentContext before use and adds a !mounted check after the first async gap in _shareUsage; the root cause is correctly addressed.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User taps Share button] --> B{captureContext == null?}
    B -- yes --> C[return early — no crash]
    B -- no --> D{!mounted?}
    D -- yes --> C
    D -- no --> E[findRenderObject as RenderRepaintBoundary]
    E --> F[await boundary.toImage]
    F --> G{!mounted?}
    G -- yes --> C
    G -- no --> H[await rootBundle.load logo\nawait instantiateImageCodec\nawait getNextFrame]
    H --> I[Draw watermark on canvas]
    I --> J[await toImage + toByteData]
    J --> K{byteData == null?}
    K -- yes --> L{mounted?}
    L -- yes --> M[show SnackBar]
    L -- no --> C
    K -- no --> N[await getTemporaryDirectory\nawait file.create\nawait file.writeAsBytes]
    N --> O[await SharePlus.share]
Loading

Comments Outside Diff (1)

  1. app/lib/pages/settings/usage_page.dart, line 71-136 (link)

    P2 Missing !mounted guards after subsequent async gaps

    A !mounted check was added after boundary.toImage(), but the function continues through many more await calls without additional guards. If the widget is disposed while rootBundle.load, instantiateImageCodec, getNextFrame, getTemporaryDirectory, file.create, or file.writeAsBytes are in flight, the function keeps executing. The ScaffoldMessenger.of(context) call at line 129 is already correctly guarded by if (mounted), so no crash will occur — but the file write and share call can still execute on a dead widget, wasting I/O and triggering a share sheet from a widget that no longer exists.

Reviews (1): Last reviewed commit: "fix usage share crash null GlobalKey con..." | Re-trigger Greptile

_screenshotKeys[_tabController.index].currentContext!.findRenderObject() as RenderRepaintBoundary;
final captureContext = _screenshotKeys[_tabController.index].currentContext;
if (captureContext == null || !mounted) return;
final RenderRepaintBoundary boundary = captureContext.findRenderObject() as RenderRepaintBoundary;
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.

P2 findRenderObject() returns RenderObject?, so the forced cast can still throw a TypeError if the render object is unexpectedly absent (e.g., the key was attached before the first frame completed). A null-safe cast makes this defensive and consistent with the null guard added just above it.

Suggested change
final RenderRepaintBoundary boundary = captureContext.findRenderObject() as RenderRepaintBoundary;
final boundary = captureContext.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return;

@kodjima33 kodjima33 merged commit a6efdbb into main May 28, 2026
3 checks passed
@kodjima33 kodjima33 deleted the fix/usage-share-null-context branch May 28, 2026 23:55
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.

2 participants