Consider the following case:
MyInheritedWidget(
identity: "a",
child: MyInheritedWidget(
identity: "b",
child: MyChildWidget(),
),
)
Inside MyChildWidget
, I can use context.dependOnInheritedWidgetOfExactType<MyInheritedWiget>()
to get the nearest instance of MyInheritedWiget
in my ancestors. Namely, the b
in this case. However, I hope there exist an approach such that I can depend on the non-nearest one, a
in this case.
For example, hope such API exists:
context.dependOnInheritedWidgetOfExactType<MyInheritedWiget>((widget) => widget.identity == 'a')
Or such API may also be OK:
context.findAllInheritedWidgetsOfExactType<MyInheritedWiget>().where((widget) => widget.identity == 'a')
And so on. But I cannot find an appropriate API like this...
Thanks for any suggestions!
CodePudding user response:
If you need to depend on the found ancestor widget, i.e. to be notified when the ancestor changes, use this approach:
- Find desired ancestor consequentially calling
getElementForInheritedWidgetOfExactType
- Call
dependOnInheritedElement
for the found element and use returned value
extension BuildContextX on BuildContext {
Iterable<InheritedElement> getElementsForInheritedWidgetsOfExactType<
T extends InheritedWidget>() sync* {
final element = getElementForInheritedWidgetOfExactType<T>();
if (element != null) {
yield element;
Element? parent;
element.visitAncestorElements((element) {
parent = element;
return false;
});
if (parent != null) {
yield* parent!.getElementsForInheritedWidgetsOfExactType<T>();
}
}
}
T? dependOnSpecificInheritedWidgetOfExactType<T extends InheritedWidget>(
bool Function(T) test,
) {
final element = getElementsForInheritedWidgetsOfExactType<T>()
.where((element) => test(element.widget as T))
.firstOrNull;
if (element == null) {
return null;
}
return dependOnInheritedElement(element) as T;
}
}
class MyWidget extends StatelessWidget {
final String identity;
...
static _MyWidget dependOnSpecific(
BuildContext context, {
required String identity,
}) {
return context.dependOnSpecificInheritedWidgetOfExactType<_MyWidget>(
(widget) => widget.identity == identity,
)!;
}
@override
Widget build(BuildContext context) {
return _MyWidget(
identity: identity,
child: ...,
);
}
}
class _MyWidget extends InheritedWidget {
...
}
Use it like this:
MyWidget.dependOnSpecific(context, identity: 'x')
Full example: https://dartpad.dev/?id=c311ca15d54dd905660fe9e2b1c8c337
CodePudding user response:
Each Element
holds HashMap<Type, InheritedElement>
. That's why we have quick access to inherited ancestors by type. But this approach limits us to one accessible widget of given type.
Another approach is iterating parents' elements until we find one that we need. This approach is implemented in findAncestorStateOfType
and findRootAncestorStateOfType
and used e.g by Navigator.of
and ScaffoldState.of
. Note that it's O(N). We can use it to find an ancestor with given property:
extension BuildContextX on BuildContext {
Iterable<T> findAncestorStatesOfType<T extends State>() sync* {
final ancestor = findAncestorStateOfType<T>();
if (ancestor != null) {
yield ancestor;
yield* ancestor.context.findAncestorStatesOfType<T>();
}
}
}
class MyWidget extends StatefulWidget {
final String identity;
...
static MyWidgetState findWith(
BuildContext context, {
required String identity,
}) {
return context
.findAncestorStatesOfType<MyWidgetState>()
.firstWhere((state) => state.widget.identity == identity);
}
@override
State<MyWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
...
}
Use it like this:
MyWidget.findWith(context, identity: 'x')
Full example: https://dartpad.dev/?id=e66d474b5685f6a2c815040ffda0b027