Skip to content

Instantly share code, notes, and snippets.

@micimize
Last active February 7, 2020 15:53
Show Gist options
  • Save micimize/36762485690b8423b553bd095654dfa7 to your computer and use it in GitHub Desktop.
Save micimize/36762485690b8423b553bd095654dfa7 to your computer and use it in GitHub Desktop.
working around the various constraints of flutter GlobalKeys + Navigators + complex pages. Fully functioning example can be seen at https://gist.github.com/micimize/5191af05191c027713a9cd1def3528db
import 'package:flutter/material.dart';
// TODO maybe GlobalObjectKeys would be better
class CheatersGlobalKey extends InheritedWidget {
CheatersGlobalKey._({
this.cheatersKeys = const {},
Key key,
Widget child,
}) : super(key: key, child: child);
final Map<String, Key> cheatersKeys;
@override
bool updateShouldNotify(CheatersGlobalKey oldWidget) {
return cheatersKeys != oldWidget.cheatersKeys;
}
static Key of(
BuildContext context,
String label,
) {
assert(context != null);
final route = ModalRoute.of(context);
// only the current route cans safely render a GlobalKey
if (!route.isCurrent) {
return Key(label);
}
// can't render a GlobalKey if it ain't there -
// this ensures the current tab gets the global key
final CheatersGlobalKey result =
context.dependOnInheritedWidgetOfExactType<CheatersGlobalKey>();
if (result == null) {
return Key(label);
}
final keys = result?.cheatersKeys ?? {};
final key = keys[label] ??
Key(label); // we default to safely returning a regular key
return key;
}
}
class CheatersIndexedStack extends StatelessWidget {
CheatersIndexedStack({
Key key,
@required this.index,
@required this.cheatersKeys,
@required this.children,
List<String> contextLabels,
}) : this.contextLabels = contextLabels ??
List<String>.generate(
children.length,
(i) => 'cheater.index=${i}',
),
super(key: key) {
assert(this.contextLabels.length == children.length);
}
final Set<String> cheatersKeys;
final List<String> contextLabels;
final List<Widget> children;
final int index;
Map<String, Key> keysFor(int childIndex) {
return index == childIndex
? Map.fromEntries(cheatersKeys.map(
(label) => MapEntry(
label,
_CheatersGlobalKey(label, contextLabels[childIndex]),
),
))
: null;
}
@override
Widget build(BuildContext context) {
return IndexedStack(
index: index,
children: children.map(withIndex((child, childIndex) {
return CheatersGlobalKey._(
// should change every tab switch
cheatersKeys: keysFor(childIndex),
child: child,
);
})).toList(),
);
}
}
class _CheatersGlobalKey extends GlobalObjectKey {
const _CheatersGlobalKey(String globalLabel, this.contextLabel)
: super(globalLabel);
final String contextLabel;
@override
String toString() {
return '[_CheatersGlobalKey#$hashCode value=$value, context=$contextLabel]';
}
}
MapFn<A, B> withIndex<A, B>(IndexedMapFn<A, B> mapFn) {
int index = -1;
return (A a) => mapFn(a, ++index);
}
@micimize
Copy link
Author

micimize commented Dec 4, 2019

The usage is something like

// in a scaffold-like
  final cheatersGlobalKeys = cheatersKeys(['backdrop', 'tabs']);

  // we need the navigators for.... navigation
  /// wrap the page content in it's navigator
  Widget pageContent(
    BuildContext context,
    int selectedIndex, [
    Widget content,
  ]) =>
      CheatersIndexedStack(
        index: selectedIndex,
        cheatersKeys: cheatersGlobalKeys,
        children: widget.pages
            .map(withIndex((page, tabIndex) => Navigator(
                  key: navStates[tabIndex],
                  onGenerateRoute: (route) => NoAnimationMaterialPageRoute(
                    settings: route,
                    maintainState: true,
                    builder: (context) => page,
                  ),
                )))
            .toList(),
      );

// in your "big" widgets:
  Widget(
      key: CheatersGlobalKey.of(context, 'backdrop'),
  )

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