Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
389a12f4
Unverified
Commit
389a12f4
authored
Nov 04, 2021
by
Hans Muller
Committed by
GitHub
Nov 04, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added widgets/AppModel (#92297)
parent
a4b51233
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
564 additions
and
11 deletions
+564
-11
app_model.0.dart
examples/api/lib/widgets/app_model/app_model.0.dart
+75
-0
app_model.1.dart
examples/api/lib/widgets/app_model/app_model.1.dart
+66
-0
app.dart
packages/flutter/lib/src/widgets/app.dart
+14
-11
app_model.dart
packages/flutter/lib/src/widgets/app_model.dart
+201
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
debug_test.dart
packages/flutter/test/material/debug_test.dart
+2
-0
app_model_test.dart
packages/flutter/test/widgets/app_model_test.dart
+205
-0
No files found.
examples/api/lib/widgets/app_model/app_model.0.dart
0 → 100644
View file @
389a12f4
// 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 AppModel
import
'package:flutter/material.dart'
;
class
ShowAppModelValue
extends
StatelessWidget
{
const
ShowAppModelValue
({
Key
?
key
,
required
this
.
appModelKey
})
:
super
(
key:
key
);
final
String
appModelKey
;
@override
Widget
build
(
BuildContext
context
)
{
// The AppModel.getValue() call here causes this widget to depend
// on the value of the AppModel's 'foo' key. If it's changed, with
// AppModel.setValue(), then this widget will be rebuilt.
final
String
value
=
AppModel
.
getValue
<
String
,
String
>(
context
,
appModelKey
,
()
=>
'initial'
);
return
Text
(
'
$appModelKey
:
$value
'
);
}
}
// Demonstrates that changes to the AppModel _only_ cause the dependent widgets
// to be rebuilt. In this case that's the ShowAppModelValue 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
ShowAppModelValue
(
appModelKey:
'foo'
),
const
SizedBox
(
height:
16
),
const
ShowAppModelValue
(
appModelKey:
'bar'
),
const
SizedBox
(
height:
16
),
ElevatedButton
(
child:
const
Text
(
'change foo'
),
onPressed:
()
{
_fooVersion
+=
1
;
// Changing the AppModel's value for 'foo' causes the widgets that
// depend on 'foo' to be rebuilt.
AppModel
.
setValue
<
String
,
String
?>(
context
,
'foo'
,
'FOO
$_fooVersion
'
);
// note: no setState()
},
),
const
SizedBox
(
height:
16
),
ElevatedButton
(
child:
const
Text
(
'change bar'
),
onPressed:
()
{
_barVersion
+=
1
;
AppModel
.
setValue
<
String
,
String
?>(
context
,
'bar'
,
'BAR
$_barVersion
'
);
// note: no setState()
},
),
],
),
),
);
}
}
void
main
(
)
{
runApp
(
const
MaterialApp
(
home:
Home
()));
}
examples/api/lib/widgets/app_model/app_model.1.dart
0 → 100644
View file @
389a12f4
// 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 AppModel
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 AppModel.set() causes dependent widgets to be rebuilt.
AppModel
.
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
AppModel
.
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
()));
}
packages/flutter/lib/src/widgets/app.dart
View file @
389a12f4
...
...
@@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
import
'package:flutter/services.dart'
;
import
'actions.dart'
;
import
'app_model.dart'
;
import
'banner.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
...
...
@@ -1664,6 +1665,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
return
RootRestorationScope
(
restorationId:
widget
.
restorationScopeId
,
child:
AppModel
(
child:
Shortcuts
(
debugLabel:
'<Default WidgetsApp Shortcuts>'
,
shortcuts:
widget
.
shortcuts
??
WidgetsApp
.
defaultShortcuts
,
...
...
@@ -1679,6 +1681,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
),
),
),
),
);
}
}
packages/flutter/lib/src/widgets/app_model.dart
0 → 100644
View file @
389a12f4
// 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 [AppModel.getValue] `init` parameter.
///
/// This callback is used to lazily create the initial value for
/// an [AppModel] keyword.
typedef
AppModelInitCallback
<
T
>
=
T
Function
();
/// Enables sharing key/value data with its `child` and all of the
/// child's descendants.
///
/// - `AppModel.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.
///
/// - `AppModel.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 AppModel.getValue(context,
/// keyword, initCallback) creates a dependency on the AppModel. When
/// the value of keyword changes with AppModel.setValue(), the widget
/// will be rebuilt. The values managed by the AppModel 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.
///
/// AppModel is not intended to be a substitute for Provider or any of
/// the other general purpose application state systems. AppModel 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 AppModel 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
/// `AppModel`. 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/app_model/app_model.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/app_model/app_model.1.dart **
/// {@end-tool}
class
AppModel
extends
StatefulWidget
{
/// Creates a widget based on [InheritedModel] that supports build
/// dependencies qualified by keywords. Descendant widgets create
/// such dependencies with [AppModel.getValue] and they trigger
/// rebuilds with [AppModel.setValue].
///
/// This widget is automatically created by the [WidgetsApp].
const
AppModel
({
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
()
=>
_AppModelState
();
/// Returns the app model's value for `key` and ensures that each
/// time the value of `key` is changed with [AppModel.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
,
AppModelInitCallback
<
V
>
init
)
{
final
_AppModelData
?
model
=
InheritedModel
.
inheritFrom
<
_AppModelData
>(
context
,
aspect:
key
);
assert
(
_debugHasAppModel
(
model
,
context
,
'getValue'
));
return
model
!.
appModelState
.
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 [AppModel.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 [AppModel.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
_AppModelData
?
model
=
context
.
getElementForInheritedWidgetOfExactType
<
_AppModelData
>()?.
widget
as
_AppModelData
?;
assert
(
_debugHasAppModel
(
model
,
context
,
'setValue'
));
model
!.
appModelState
.
setValue
<
K
,
V
>(
key
,
value
);
}
static
bool
_debugHasAppModel
(
_AppModelData
?
model
,
BuildContext
context
,
String
methodName
)
{
assert
(()
{
if
(
model
==
null
)
{
throw
FlutterError
.
fromParts
(
<
DiagnosticsNode
>[
ErrorSummary
(
'No AppModel widget found.'
),
ErrorDescription
(
'AppModel.
$methodName
requires an AppModel widget ancestor.
\n
'
),
context
.
describeWidget
(
'The specific widget that could not find an AppModel ancestor was'
),
context
.
describeOwnershipChain
(
'The ownership chain for the affected widget is'
),
ErrorHint
(
'Typically, the AppModel 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
_AppModelState
extends
State
<
AppModel
>
{
late
Map
<
Object
,
Object
?>
data
=
<
Object
,
Object
?>{};
@override
Widget
build
(
BuildContext
context
)
{
return
_AppModelData
(
appModelState:
this
,
child:
widget
.
child
);
}
V
getValue
<
K
extends
Object
,
V
>(
K
key
,
AppModelInitCallback
<
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
_AppModelData
extends
InheritedModel
<
Object
>
{
_AppModelData
({
Key
?
key
,
required
this
.
appModelState
,
required
Widget
child
})
:
data
=
appModelState
.
data
,
super
(
key:
key
,
child:
child
);
final
_AppModelState
appModelState
;
final
Map
<
Object
,
Object
?>
data
;
@override
bool
updateShouldNotify
(
_AppModelData
old
)
{
return
data
!=
old
.
data
;
}
@override
bool
updateShouldNotifyDependent
(
_AppModelData
old
,
Set
<
Object
>
keys
)
{
for
(
final
Object
key
in
keys
)
{
if
(
data
[
key
]
!=
old
.
data
[
key
])
{
return
true
;
}
}
return
false
;
}
}
packages/flutter/lib/widgets.dart
View file @
389a12f4
...
...
@@ -22,6 +22,7 @@ export 'src/widgets/animated_size.dart';
export
'src/widgets/animated_switcher.dart'
;
export
'src/widgets/annotated_region.dart'
;
export
'src/widgets/app.dart'
;
export
'src/widgets/app_model.dart'
;
export
'src/widgets/async.dart'
;
export
'src/widgets/autocomplete.dart'
;
export
'src/widgets/autofill.dart'
;
...
...
packages/flutter/test/material/debug_test.dart
View file @
389a12f4
...
...
@@ -200,6 +200,8 @@ void main() {
' _FocusMarker
\n
'
' Focus
\n
'
' Shortcuts
\n
'
' _AppModelData
\n
'
' AppModel
\n
'
' UnmanagedRestorationScope
\n
'
' RestorationScope
\n
'
' UnmanagedRestorationScope
\n
'
...
...
packages/flutter/test/widgets/app_model_test.dart
0 → 100644
View file @
389a12f4
// 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
(
'AppModel basics'
,
(
WidgetTester
tester
)
async
{
int
columnBuildCount
=
0
;
int
child1BuildCount
=
0
;
int
child2BuildCount
=
0
;
late
void
Function
(
BuildContext
context
)
setAppModelValue
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
AppModel
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
columnBuildCount
+=
1
;
return
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
()
{
setAppModelValue
.
call
(
context
);
},
child:
Column
(
children:
<
Widget
>[
Builder
(
builder:
(
BuildContext
context
)
{
child1BuildCount
+=
1
;
return
Text
(
AppModel
.
getValue
<
String
,
String
>(
context
,
'child1Text'
,
()
=>
'null'
));
},
),
Builder
(
builder:
(
BuildContext
context
)
{
child2BuildCount
+=
1
;
return
Text
(
AppModel
.
getValue
<
String
,
String
>(
context
,
'child2Text'
,
()
=>
'null'
));
}
),
],
),
);
},
),
),
),
);
expect
(
columnBuildCount
,
1
);
expect
(
child1BuildCount
,
1
);
expect
(
child2BuildCount
,
1
);
expect
(
find
.
text
(
'null'
).
evaluate
().
length
,
2
);
// AppModel.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.
setAppModelValue
=
(
BuildContext
context
)
{
AppModel
.
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
);
// AppModel.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.
setAppModelValue
=
(
BuildContext
context
)
{
AppModel
.
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.
setAppModelValue
=
(
BuildContext
context
)
{
AppModel
.
setValue
<
String
,
String
>(
context
,
'child1Text'
,
'child1'
);
AppModel
.
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..
setAppModelValue
=
(
BuildContext
context
)
{
AppModel
.
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
);
setAppModelValue
=
(
BuildContext
context
)
{
AppModel
.
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 AppModel '
,
(
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:
()
{
AppModel
.
setValue
<
String
,
String
>(
context
,
'childText'
,
'child'
);
},
child:
Center
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
childBuildCount
+=
1
;
return
Text
(
AppModel
.
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 AppModel 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
;
AppModel
.
setValue
<
String
,
String
>(
context
,
'childText'
,
'child'
);
},
child:
Center
(
child:
AppModel
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
innerTapCount
+=
1
;
AppModel
.
setValue
<
String
,
String
>(
context
,
'childText'
,
'child'
);
},
child:
Text
(
AppModel
.
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
);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment