mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Added SharedAppData to the widgets library (#93175)
This commit is contained in:
parent
6bc33812ba
commit
f4f23ecb59
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for SharedAppData
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ShowSharedValue extends StatelessWidget {
|
||||||
|
const ShowSharedValue({ Key? key, required this.appDataKey }) : super(key: key);
|
||||||
|
|
||||||
|
final String appDataKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// The SharedAppData.getValue() call here causes this widget to depend
|
||||||
|
// on the value of the SharedAppData's 'foo' key. If it's changed, with
|
||||||
|
// SharedAppData.setValue(), then this widget will be rebuilt.
|
||||||
|
final String value = SharedAppData.getValue<String, String>(context, appDataKey, () => 'initial');
|
||||||
|
return Text('$appDataKey: $value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demonstrates that changes to the SharedAppData _only_ cause the dependent widgets
|
||||||
|
// to be rebuilt. In this case that's the ShowSharedValue widget that's
|
||||||
|
// displaying the value of a key whose value has been updated.
|
||||||
|
class Home extends StatefulWidget {
|
||||||
|
const Home({ Key? key }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Home> createState() => _HomeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeState extends State<Home> {
|
||||||
|
int _fooVersion = 0;
|
||||||
|
int _barVersion = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const ShowSharedValue(appDataKey: 'foo'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const ShowSharedValue(appDataKey: 'bar'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('change foo'),
|
||||||
|
onPressed: () {
|
||||||
|
_fooVersion += 1;
|
||||||
|
// Changing the SharedAppData's value for 'foo' causes the widgets that
|
||||||
|
// depend on 'foo' to be rebuilt.
|
||||||
|
SharedAppData.setValue<String, String?>(context, 'foo', 'FOO $_fooVersion'); // note: no setState()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('change bar'),
|
||||||
|
onPressed: () {
|
||||||
|
_barVersion += 1;
|
||||||
|
SharedAppData.setValue<String, String?>(context, 'bar', 'BAR $_barVersion'); // note: no setState()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MaterialApp(home: Home()));
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for SharedAppData
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// A single lazily constructed object that's shared with the entire
|
||||||
|
// application via `SharedObject.of(context)`. The value of the object
|
||||||
|
// can be changed with `SharedObject.reset(context)`. Resetting the value
|
||||||
|
// will cause all of the widgets that depend on it to be rebuilt.
|
||||||
|
class SharedObject {
|
||||||
|
SharedObject._();
|
||||||
|
|
||||||
|
static final Object _sharedObjectKey = Object();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describeIdentity(this);
|
||||||
|
|
||||||
|
static void reset(BuildContext context) {
|
||||||
|
// Calling SharedAppData.setValue() causes dependent widgets to be rebuilt.
|
||||||
|
SharedAppData.setValue<Object, SharedObject>(context, _sharedObjectKey, SharedObject._());
|
||||||
|
}
|
||||||
|
|
||||||
|
static SharedObject of(BuildContext context) {
|
||||||
|
// If a value for _sharedObjectKey has never been set then the third
|
||||||
|
// callback parameter is used to generate an initial value.
|
||||||
|
return SharedAppData.getValue<Object, SharedObject>(context, _sharedObjectKey, () => SharedObject._());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An example of a widget which depends on the SharedObject's value,
|
||||||
|
// which might be provided - along with SharedObject - in a Dart package.
|
||||||
|
class CustomWidget extends StatelessWidget {
|
||||||
|
const CustomWidget({ Key? key }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Will be rebuilt if the shared object's value is changed.
|
||||||
|
return ElevatedButton(
|
||||||
|
child: Text('Replace ${SharedObject.of(context)}'),
|
||||||
|
onPressed: () {
|
||||||
|
SharedObject.reset(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Home extends StatelessWidget {
|
||||||
|
const Home({ Key? key }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CustomWidget()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MaterialApp(home: Home()));
|
||||||
|
}
|
@ -24,6 +24,7 @@ import 'restoration.dart';
|
|||||||
import 'router.dart';
|
import 'router.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
import 'semantics_debugger.dart';
|
import 'semantics_debugger.dart';
|
||||||
|
import 'shared_app_data.dart';
|
||||||
import 'shortcuts.dart';
|
import 'shortcuts.dart';
|
||||||
import 'text.dart';
|
import 'text.dart';
|
||||||
import 'title.dart';
|
import 'title.dart';
|
||||||
@ -1664,17 +1665,19 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
return RootRestorationScope(
|
return RootRestorationScope(
|
||||||
restorationId: widget.restorationScopeId,
|
restorationId: widget.restorationScopeId,
|
||||||
child: Shortcuts(
|
child: SharedAppData(
|
||||||
debugLabel: '<Default WidgetsApp Shortcuts>',
|
child: Shortcuts(
|
||||||
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
|
debugLabel: '<Default WidgetsApp Shortcuts>',
|
||||||
// DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
|
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
|
||||||
// fall through to the defaultShortcuts.
|
// DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
|
||||||
child: DefaultTextEditingShortcuts(
|
// fall through to the defaultShortcuts.
|
||||||
child: Actions(
|
child: DefaultTextEditingShortcuts(
|
||||||
actions: widget.actions ?? WidgetsApp.defaultActions,
|
child: Actions(
|
||||||
child: FocusTraversalGroup(
|
actions: widget.actions ?? WidgetsApp.defaultActions,
|
||||||
policy: ReadingOrderTraversalPolicy(),
|
child: FocusTraversalGroup(
|
||||||
child: child,
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
201
packages/flutter/lib/src/widgets/shared_app_data.dart
Normal file
201
packages/flutter/lib/src/widgets/shared_app_data.dart
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'framework.dart';
|
||||||
|
import 'inherited_model.dart';
|
||||||
|
|
||||||
|
/// The type of the [SharedAppData.getValue] `init` parameter.
|
||||||
|
///
|
||||||
|
/// This callback is used to lazily create the initial value for
|
||||||
|
/// a [SharedAppData] keyword.
|
||||||
|
typedef SharedAppDataInitCallback<T> = T Function();
|
||||||
|
|
||||||
|
/// Enables sharing key/value data with its `child` and all of the
|
||||||
|
/// child's descendants.
|
||||||
|
///
|
||||||
|
/// - `SharedAppData.getValue(context, key, initCallback)` creates a dependency
|
||||||
|
/// on the key and returns the value for the key from the shared data table.
|
||||||
|
/// If no value exists for key then the initCallback is used to create
|
||||||
|
/// the initial value.
|
||||||
|
///
|
||||||
|
/// - `SharedAppData.setValue(context, key, value)` changes the value of an entry
|
||||||
|
/// in the shared data table and forces widgets that depend on that entry
|
||||||
|
/// to be rebuilt.
|
||||||
|
///
|
||||||
|
/// A widget whose build method uses SharedAppData.getValue(context,
|
||||||
|
/// keyword, initCallback) creates a dependency on the SharedAppData. When
|
||||||
|
/// the value of keyword changes with SharedAppData.setValue(), the widget
|
||||||
|
/// will be rebuilt. The values managed by the SharedAppData are expected
|
||||||
|
/// to be immutable: intrinsic changes to values will not cause
|
||||||
|
/// dependent widgets to be rebuilt.
|
||||||
|
///
|
||||||
|
/// An instance of this widget is created automatically by [WidgetsApp].
|
||||||
|
///
|
||||||
|
/// There are many ways to share data with a widget subtree. This
|
||||||
|
/// class is based on [InheritedModel], which is an [InheritedWidget].
|
||||||
|
/// It's intended to be used by packages that need to share a modest
|
||||||
|
/// number of values among their own components.
|
||||||
|
///
|
||||||
|
/// SharedAppData is not intended to be a substitute for Provider or any of
|
||||||
|
/// the other general purpose application state systems. SharedAppData is
|
||||||
|
/// for situations where a package's custom widgets need to share one
|
||||||
|
/// or a handful of immutable data objects that can be lazily
|
||||||
|
/// initialized. It exists so that packages like that can deliver
|
||||||
|
/// custom widgets without requiring the developer to add a
|
||||||
|
/// package-specific umbrella widget to their application.
|
||||||
|
///
|
||||||
|
/// A good way to create an SharedAppData key that avoids potential
|
||||||
|
/// collisions with other packages is to use a static `Object()` value.
|
||||||
|
/// The `SharedObject` example below does this.
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// The following sample demonstrates using the automatically created
|
||||||
|
/// `SharedAppData`. Button presses cause changes to the values for keys
|
||||||
|
/// 'foo', and 'bar', and those changes only cause the widgets that
|
||||||
|
/// depend on those keys to be rebuilt.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// The following sample demonstrates how a single lazily computed
|
||||||
|
/// value could be shared within an app. A Flutter package that
|
||||||
|
/// provided custom widgets might use this approach to share a (possibly
|
||||||
|
/// private) value with instances of those widgets.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
class SharedAppData extends StatefulWidget {
|
||||||
|
/// Creates a widget based on [InheritedModel] that supports build
|
||||||
|
/// dependencies qualified by keywords. Descendant widgets create
|
||||||
|
/// such dependencies with [SharedAppData.getValue] and they trigger
|
||||||
|
/// rebuilds with [SharedAppData.setValue].
|
||||||
|
///
|
||||||
|
/// This widget is automatically created by the [WidgetsApp].
|
||||||
|
const SharedAppData({ Key? key, required this.child }) : super(key: key);
|
||||||
|
|
||||||
|
/// The widget below this widget in the tree.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _SharedAppDataState();
|
||||||
|
|
||||||
|
/// Returns the app model's value for `key` and ensures that each
|
||||||
|
/// time the value of `key` is changed with [SharedAppData.setValue], the
|
||||||
|
/// specified context will be rebuilt.
|
||||||
|
///
|
||||||
|
/// If no value for `key` exists then the `init` callback is used to
|
||||||
|
/// generate an initial value. The callback is expected to return
|
||||||
|
/// an immutable value because intrinsic changes to the value will
|
||||||
|
/// not cause dependent widgets to be rebuilt.
|
||||||
|
///
|
||||||
|
/// A widget that depends on the app model's value for `key` should use
|
||||||
|
/// this method in their `build` methods to ensure that they are rebuilt
|
||||||
|
/// if the value changes.
|
||||||
|
///
|
||||||
|
/// The type parameter `K` is the type of the keyword and `V`
|
||||||
|
/// is the type of the value.
|
||||||
|
static V getValue<K extends Object, V>(BuildContext context, K key, SharedAppDataInitCallback<V> init) {
|
||||||
|
final _SharedAppModel? model = InheritedModel.inheritFrom<_SharedAppModel>(context, aspect: key);
|
||||||
|
assert(_debugHasSharedAppData(model, context, 'getValue'));
|
||||||
|
return model!.sharedAppDataState.getValue<K, V>(key, init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the app model's `value` for `key` and rebuilds any widgets
|
||||||
|
/// that have created a dependency on `key` with [SharedAppData.getValue].
|
||||||
|
///
|
||||||
|
/// If `value` is `==` to the current value of `key` then nothing
|
||||||
|
/// is rebuilt.
|
||||||
|
///
|
||||||
|
/// The `value` is expected to be immutable because intrinsic
|
||||||
|
/// changes to the value will not cause dependent widgets to be
|
||||||
|
/// rebuilt.
|
||||||
|
///
|
||||||
|
/// Unlike [SharedAppData.getValue], this method does _not_ create a dependency
|
||||||
|
/// between `context` and `key`.
|
||||||
|
///
|
||||||
|
/// The type parameter `K` is the type of the value's keyword and `V`
|
||||||
|
/// is the type of the value.
|
||||||
|
static void setValue<K extends Object, V>(BuildContext context, K key, V value) {
|
||||||
|
final _SharedAppModel? model = context.getElementForInheritedWidgetOfExactType<_SharedAppModel>()?.widget as _SharedAppModel?;
|
||||||
|
assert(_debugHasSharedAppData(model, context, 'setValue'));
|
||||||
|
model!.sharedAppDataState.setValue<K, V>(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _debugHasSharedAppData(_SharedAppModel? model, BuildContext context, String methodName) {
|
||||||
|
assert(() {
|
||||||
|
if (model == null) {
|
||||||
|
throw FlutterError.fromParts(
|
||||||
|
<DiagnosticsNode>[
|
||||||
|
ErrorSummary('No SharedAppData widget found.'),
|
||||||
|
ErrorDescription('SharedAppData.$methodName requires an SharedAppData widget ancestor.\n'),
|
||||||
|
context.describeWidget('The specific widget that could not find an SharedAppData ancestor was'),
|
||||||
|
context.describeOwnershipChain('The ownership chain for the affected widget is'),
|
||||||
|
ErrorHint(
|
||||||
|
'Typically, the SharedAppData widget is introduced by the MaterialApp '
|
||||||
|
'or WidgetsApp widget at the top of your application widget tree. It '
|
||||||
|
'provides a key/value map of data that is shared with the entire '
|
||||||
|
'application.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SharedAppDataState extends State<SharedAppData> {
|
||||||
|
late Map<Object, Object?> data = <Object, Object?>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _SharedAppModel(sharedAppDataState: this, child: widget.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
V getValue<K extends Object, V>(K key, SharedAppDataInitCallback<V> init) {
|
||||||
|
data[key] ??= init();
|
||||||
|
return data[key] as V;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValue<K extends Object, V>(K key, V value) {
|
||||||
|
if (data[key] != value) {
|
||||||
|
setState(() {
|
||||||
|
data = Map<Object, Object?>.from(data);
|
||||||
|
data[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SharedAppModel extends InheritedModel<Object> {
|
||||||
|
_SharedAppModel({
|
||||||
|
Key? key,
|
||||||
|
required this.sharedAppDataState,
|
||||||
|
required Widget child
|
||||||
|
}) : data = sharedAppDataState.data, super(key: key, child: child);
|
||||||
|
|
||||||
|
final _SharedAppDataState sharedAppDataState;
|
||||||
|
final Map<Object, Object?> data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(_SharedAppModel old) {
|
||||||
|
return data != old.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotifyDependent(_SharedAppModel old, Set<Object> keys) {
|
||||||
|
for (final Object key in keys) {
|
||||||
|
if (data[key] != old.data[key]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -107,6 +107,7 @@ export 'src/widgets/scroll_view.dart';
|
|||||||
export 'src/widgets/scrollable.dart';
|
export 'src/widgets/scrollable.dart';
|
||||||
export 'src/widgets/scrollbar.dart';
|
export 'src/widgets/scrollbar.dart';
|
||||||
export 'src/widgets/semantics_debugger.dart';
|
export 'src/widgets/semantics_debugger.dart';
|
||||||
|
export 'src/widgets/shared_app_data.dart';
|
||||||
export 'src/widgets/shortcuts.dart';
|
export 'src/widgets/shortcuts.dart';
|
||||||
export 'src/widgets/single_child_scroll_view.dart';
|
export 'src/widgets/single_child_scroll_view.dart';
|
||||||
export 'src/widgets/size_changed_layout_notifier.dart';
|
export 'src/widgets/size_changed_layout_notifier.dart';
|
||||||
|
@ -200,6 +200,8 @@ void main() {
|
|||||||
' _FocusMarker\n'
|
' _FocusMarker\n'
|
||||||
' Focus\n'
|
' Focus\n'
|
||||||
' Shortcuts\n'
|
' Shortcuts\n'
|
||||||
|
' _SharedAppModel\n'
|
||||||
|
' SharedAppData\n'
|
||||||
' UnmanagedRestorationScope\n'
|
' UnmanagedRestorationScope\n'
|
||||||
' RestorationScope\n'
|
' RestorationScope\n'
|
||||||
' UnmanagedRestorationScope\n'
|
' UnmanagedRestorationScope\n'
|
||||||
|
205
packages/flutter/test/widgets/shared_app_data_test.dart
Normal file
205
packages/flutter/test/widgets/shared_app_data_test.dart
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('SharedAppData basics', (WidgetTester tester) async {
|
||||||
|
int columnBuildCount = 0;
|
||||||
|
int child1BuildCount = 0;
|
||||||
|
int child2BuildCount = 0;
|
||||||
|
late void Function(BuildContext context) setSharedAppDataValue;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: SharedAppData(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
columnBuildCount += 1;
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
setSharedAppDataValue.call(context);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
child1BuildCount += 1;
|
||||||
|
return Text(SharedAppData.getValue<String, String>(context, 'child1Text', () => 'null'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
child2BuildCount += 1;
|
||||||
|
return Text(SharedAppData.getValue<String, String>(context, 'child2Text', () => 'null'));
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 1);
|
||||||
|
expect(child2BuildCount, 1);
|
||||||
|
expect(find.text('null').evaluate().length, 2);
|
||||||
|
|
||||||
|
// SharedAppData.setValue<String, String>(context, 'child1Text', 'child1')
|
||||||
|
// causes the first Text widget to be rebuilt with its text to be
|
||||||
|
// set to 'child1'. Nothing else is rebuilt.
|
||||||
|
setSharedAppDataValue = (BuildContext context) {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
|
||||||
|
};
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 2);
|
||||||
|
expect(child2BuildCount, 1);
|
||||||
|
expect(find.text('child1'), findsOneWidget);
|
||||||
|
expect(find.text('null'), findsOneWidget);
|
||||||
|
|
||||||
|
// SharedAppData.setValue<String, String>(context, 'child2Text', 'child1')
|
||||||
|
// causes the second Text widget to be rebuilt with its text to be
|
||||||
|
// set to 'child2'. Nothing else is rebuilt.
|
||||||
|
setSharedAppDataValue = (BuildContext context) {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
|
||||||
|
};
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 2);
|
||||||
|
expect(child2BuildCount, 2);
|
||||||
|
expect(find.text('child1'), findsOneWidget);
|
||||||
|
expect(find.text('child2'), findsOneWidget);
|
||||||
|
|
||||||
|
// Resetting a key's value to the same value does not
|
||||||
|
// cause any widgets to be rebuilt.
|
||||||
|
setSharedAppDataValue = (BuildContext context) {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
|
||||||
|
};
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 2);
|
||||||
|
expect(child2BuildCount, 2);
|
||||||
|
|
||||||
|
// More of the same, resetting the values to null..
|
||||||
|
|
||||||
|
setSharedAppDataValue = (BuildContext context) {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child1Text', 'null');
|
||||||
|
};
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 3);
|
||||||
|
expect(child2BuildCount, 2);
|
||||||
|
expect(find.text('null'), findsOneWidget);
|
||||||
|
expect(find.text('child2'), findsOneWidget);
|
||||||
|
|
||||||
|
setSharedAppDataValue = (BuildContext context) {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'child2Text', 'null');
|
||||||
|
};
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(columnBuildCount, 1);
|
||||||
|
expect(child1BuildCount, 3);
|
||||||
|
expect(child2BuildCount, 3);
|
||||||
|
expect(find.text('null').evaluate().length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('WidgetsApp SharedAppData ', (WidgetTester tester) async {
|
||||||
|
int parentBuildCount = 0;
|
||||||
|
int childBuildCount = 0;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
WidgetsApp(
|
||||||
|
color: const Color(0xff00ff00),
|
||||||
|
builder: (BuildContext context, Widget? child) {
|
||||||
|
parentBuildCount += 1;
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
SharedAppData.setValue<String, String>(context, 'childText', 'child');
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
childBuildCount += 1;
|
||||||
|
return Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('null'), findsOneWidget);
|
||||||
|
expect(parentBuildCount, 1);
|
||||||
|
expect(childBuildCount, 1);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(GestureDetector));
|
||||||
|
await tester.pump();
|
||||||
|
expect(parentBuildCount, 1);
|
||||||
|
expect(childBuildCount, 2);
|
||||||
|
expect(find.text('child'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('WidgetsApp SharedAppData Shadowing', (WidgetTester tester) async {
|
||||||
|
int innerTapCount = 0;
|
||||||
|
int outerTapCount = 0;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
WidgetsApp(
|
||||||
|
color: const Color(0xff00ff00),
|
||||||
|
builder: (BuildContext context, Widget? child) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
outerTapCount += 1;
|
||||||
|
SharedAppData.setValue<String, String>(context, 'childText', 'child');
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: SharedAppData(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
innerTapCount += 1;
|
||||||
|
SharedAppData.setValue<String, String>(context, 'childText', 'child');
|
||||||
|
},
|
||||||
|
child: Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('null'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tapAt(const Offset(10, 10));
|
||||||
|
await tester.pump();
|
||||||
|
expect(outerTapCount, 1);
|
||||||
|
expect(innerTapCount, 0);
|
||||||
|
expect(find.text('null'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('null'));
|
||||||
|
await tester.pump();
|
||||||
|
expect(outerTapCount, 1);
|
||||||
|
expect(innerTapCount, 1);
|
||||||
|
expect(find.text('child'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user