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
ea355c66
Unverified
Commit
ea355c66
authored
Aug 16, 2018
by
xster
Committed by
GitHub
Aug 16, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create a ValueListenableBuilder (#19729)
parent
f62e6d9e
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
247 additions
and
0 deletions
+247
-0
value_listenable_builder.dart
...ges/flutter/lib/src/widgets/value_listenable_builder.dart
+126
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
value_listenable_builder_test.dart
...s/flutter/test/widgets/value_listenable_builder_test.dart
+120
-0
No files found.
packages/flutter/lib/src/widgets/value_listenable_builder.dart
0 → 100644
View file @
ea355c66
// Copyright 2018 The Chromium 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'
;
/// Builds a [Widget] when given a concrete value of a [ValueListenable<T>].
///
/// If the `child` parameter provided to the [ValueListenableBuilder] is not
/// null, the same `child` widget is passed back to this [ValueWidgetBuilder]
/// and should typically be incorporated in the returned widget tree.
///
/// See also:
///
/// * [ValueListenableBuilder], a widget which invokes this builder each time
/// a [ValueListenable] changes value.
typedef
Widget
ValueWidgetBuilder
<
T
>(
BuildContext
context
,
T
value
,
Widget
child
);
/// A widget whose content stays sync'ed with a [ValueListenable].
///
/// Given a [ValueListenable<T>] and a [builder] which builds widgets from
/// concrete values of `T`, this class will automatically register itself as a
/// listener of the [ValueListenable] and call the [builder] with updated values
/// when the value changes.
///
/// ## Performance optimizations
///
/// If your [builder] function contains a subtree that does not depend on the
/// value of the [ValueListenable], it's more efficient to build that subtree
/// once instead of rebuilding it on every animation tick.
///
/// If you pass the pre-built subtree as the [child] parameter, the
/// [ValueListenableBuilder] will pass it back to your [builder] function so
/// that you can incorporate it into your build.
///
/// Using this pre-built child is entirely optional, but can improve
/// performance significantly in some cases and is therefore a good practice.
///
/// See also:
///
/// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable]
/// without passing back a specific value from a [ValueListenable].
/// * [NotificationListener], which lets you rebuild based on [Notification]
/// coming from its descendent widgets rather than a [ValueListenable] that
/// you have a direct reference to.
/// * [StreamBuilder], where a builder can depend on a [Stream] rather than
/// a [ValueListenable] for more advanced use cases.
class
ValueListenableBuilder
<
T
>
extends
StatefulWidget
{
/// Creates a [ValueListenableBuilder].
///
/// The [valueListenable] and [builder] arguments must not be null.
/// The [child] is optional but is good practice to use if part of the widget
/// subtree does not depend on the value of the [valueListenable].
const
ValueListenableBuilder
({
@required
this
.
valueListenable
,
@required
this
.
builder
,
this
.
child
,
})
:
assert
(
valueListenable
!=
null
),
assert
(
builder
!=
null
);
/// The [ValueListenable] whose value you depend on in order to build.
///
/// This widget does not ensure that the [ValueListenable]'s value is not
/// null, therefore your [builder] may need to handle null values.
///
/// This [ValueListenable] itself must not be null.
final
ValueListenable
<
T
>
valueListenable
;
/// A [ValueWidgetBuilder] which builds a widget depending on the
/// [valueListenable]'s value.
///
/// Can incorporate a [valueListenable] value-independent widget subtree
/// from the [child] parameter into the returned widget tree.
///
/// Must not be null.
final
ValueWidgetBuilder
<
T
>
builder
;
/// A [valueListenable]-independent widget which is passed back to the [builder].
///
/// This argument is optional and can be null if the entire widget subtree
/// the [builder] builds depends on the value of the [valueListenable]. For
/// example, if the [valueListenable] is a [String] and the [builder] simply
/// returns a [Text] widget with the [String] value.
final
Widget
child
;
@override
State
<
StatefulWidget
>
createState
()
=>
new
_ValueListenableBuilderState
<
T
>();
}
class
_ValueListenableBuilderState
<
T
>
extends
State
<
ValueListenableBuilder
<
T
>>
{
T
value
;
@override
void
initState
()
{
super
.
initState
();
value
=
widget
.
valueListenable
.
value
;
widget
.
valueListenable
.
addListener
(
_valueChanged
);
}
@override
void
didUpdateWidget
(
ValueListenableBuilder
<
T
>
oldWidget
)
{
if
(
oldWidget
.
valueListenable
!=
widget
.
valueListenable
)
{
oldWidget
.
valueListenable
.
removeListener
(
_valueChanged
);
value
=
widget
.
valueListenable
.
value
;
widget
.
valueListenable
.
addListener
(
_valueChanged
);
}
super
.
didUpdateWidget
(
oldWidget
);
}
@override
void
dispose
()
{
widget
.
valueListenable
.
removeListener
(
_valueChanged
);
super
.
dispose
();
}
void
_valueChanged
()
{
setState
(()
{
value
=
widget
.
valueListenable
.
value
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
widget
.
builder
(
context
,
value
,
widget
.
child
);
}
}
packages/flutter/lib/widgets.dart
View file @
ea355c66
...
...
@@ -98,6 +98,7 @@ export 'src/widgets/ticker_provider.dart';
export
'src/widgets/title.dart'
;
export
'src/widgets/transitions.dart'
;
export
'src/widgets/unique_widget.dart'
;
export
'src/widgets/value_listenable_builder.dart'
;
export
'src/widgets/viewport.dart'
;
export
'src/widgets/visibility.dart'
;
export
'src/widgets/widget_inspector.dart'
;
...
...
packages/flutter/test/widgets/value_listenable_builder_test.dart
0 → 100644
View file @
ea355c66
// Copyright 2018 The Chromium 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_test/flutter_test.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
void
main
(
)
{
SpyStringValueNotifier
valueListenable
;
Widget
textBuilderUnderTest
;
Widget
builderForValueListenable
(
ValueListenable
<
String
>
valueListenable
,
)
{
return
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
ValueListenableBuilder
<
String
>(
valueListenable:
valueListenable
,
builder:
(
BuildContext
context
,
String
value
,
Widget
child
)
{
if
(
value
==
null
)
return
const
Placeholder
();
return
new
Text
(
value
);
},
),
);
}
setUp
(()
{
valueListenable
=
new
SpyStringValueNotifier
(
null
);
textBuilderUnderTest
=
builderForValueListenable
(
valueListenable
);
});
testWidgets
(
'Null value is ok'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
textBuilderUnderTest
);
expect
(
find
.
byType
(
Placeholder
),
findsOneWidget
);
});
testWidgets
(
'Widget builds with initial value'
,
(
WidgetTester
tester
)
async
{
valueListenable
=
new
SpyStringValueNotifier
(
'Bachman'
);
await
tester
.
pumpWidget
(
builderForValueListenable
(
valueListenable
));
expect
(
find
.
text
(
'Bachman'
),
findsOneWidget
);
});
testWidgets
(
'Widget updates when value changes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
textBuilderUnderTest
);
valueListenable
.
value
=
'Gilfoyle'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'Gilfoyle'
),
findsOneWidget
);
valueListenable
.
value
=
'Dinesh'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'Gilfoyle'
),
findsNothing
);
expect
(
find
.
text
(
'Dinesh'
),
findsOneWidget
);
});
testWidgets
(
'Can change listenable'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
textBuilderUnderTest
);
valueListenable
.
value
=
'Gilfoyle'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'Gilfoyle'
),
findsOneWidget
);
final
ValueListenable
<
String
>
differentListenable
=
new
SpyStringValueNotifier
(
'Hendricks'
);
await
tester
.
pumpWidget
(
builderForValueListenable
(
differentListenable
));
expect
(
find
.
text
(
'Gilfoyle'
),
findsNothing
);
expect
(
find
.
text
(
'Hendricks'
),
findsOneWidget
);
});
testWidgets
(
'Stops listening to old listenable after chainging listenable'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
textBuilderUnderTest
);
valueListenable
.
value
=
'Gilfoyle'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'Gilfoyle'
),
findsOneWidget
);
final
ValueListenable
<
String
>
differentListenable
=
new
SpyStringValueNotifier
(
'Hendricks'
);
await
tester
.
pumpWidget
(
builderForValueListenable
(
differentListenable
));
expect
(
find
.
text
(
'Gilfoyle'
),
findsNothing
);
expect
(
find
.
text
(
'Hendricks'
),
findsOneWidget
);
// Change value of the (now) disconnected listenable.
valueListenable
.
value
=
'Big Head'
;
expect
(
find
.
text
(
'Gilfoyle'
),
findsNothing
);
expect
(
find
.
text
(
'Big Head'
),
findsNothing
);
expect
(
find
.
text
(
'Hendricks'
),
findsOneWidget
);
});
testWidgets
(
'Self-cleans when removed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
textBuilderUnderTest
);
valueListenable
.
value
=
'Gilfoyle'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'Gilfoyle'
),
findsOneWidget
);
await
tester
.
pumpWidget
(
const
Placeholder
());
expect
(
find
.
text
(
'Gilfoyle'
),
findsNothing
);
expect
(
valueListenable
.
hasListeners
,
false
);
});
}
class
SpyStringValueNotifier
extends
ValueNotifier
<
String
>
{
SpyStringValueNotifier
(
String
initialValue
)
:
super
(
initialValue
);
/// Override for test visibility only.
@override
bool
get
hasListeners
=>
super
.
hasListeners
;
}
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