Skip to content

Commit 0825603

Browse files
youssef-attiagspencergoogdkwingsmt
authored
Added an example for IndexedStack widget (#105318)
* Added an example for IndexedStack * Added tests for the IndexedStack example * Fixed type issue for onSubmitted callback functions * Fixed documentation and moved files to their appropriate places * Fixed documentation and moved files to their appropriate places * Moved test files to their appropriate places * Moved test files to their appropriate places * Fixed file path in documentation * Remove trailing space * Formatting changes * Remove extra line * Further formatting changes * Further formatting changes * fix comma and inline Co-authored-by: Greg Spencer <[email protected]> * Formatting * indentation and formatting * Formatting * Formatting * Formatting * Removed duplicate chevron * better wording on documentation Co-authored-by: Tong Mu <[email protected]> * Added testing for state preservation Co-authored-by: Greg Spencer <[email protected]> Co-authored-by: Tong Mu <[email protected]>
1 parent 38df107 commit 0825603

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for IndexedStack.
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const MyApp());
10+
11+
class MyApp extends StatelessWidget {
12+
const MyApp({super.key});
13+
14+
static const String _title = 'Flutter Code Sample';
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return MaterialApp(
19+
title: _title,
20+
home: Scaffold(
21+
appBar: AppBar(title: const Text(_title)),
22+
body: const MyStatefulWidget(),
23+
),
24+
);
25+
}
26+
}
27+
28+
class MyStatefulWidget extends StatefulWidget {
29+
const MyStatefulWidget({super.key});
30+
31+
@override
32+
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
33+
}
34+
35+
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
36+
List<String> names = <String>['Dash', 'John', 'Mary'];
37+
int index = 0;
38+
final TextEditingController fieldText = TextEditingController();
39+
40+
@override
41+
Widget build(BuildContext context) {
42+
return Column(
43+
mainAxisAlignment: MainAxisAlignment.center,
44+
children: <Widget>[
45+
SizedBox(
46+
width: 300,
47+
child: TextField(
48+
decoration: const InputDecoration(
49+
border: OutlineInputBorder(),
50+
hintText: 'Enter the name for a person to track',
51+
),
52+
onSubmitted: (String value) {
53+
setState(() {
54+
names.add(value);
55+
});
56+
fieldText.clear();
57+
},
58+
controller: fieldText,
59+
),
60+
),
61+
const SizedBox(height: 50),
62+
Row(
63+
mainAxisAlignment: MainAxisAlignment.center,
64+
children: <Widget>[
65+
GestureDetector(
66+
onTap: () {
67+
setState(() {
68+
if (index == 0) {
69+
index = names.length - 1;
70+
} else {
71+
index -= 1;
72+
}
73+
});
74+
},
75+
child: const Icon(key: Key('gesture1'), Icons.chevron_left),
76+
),
77+
Column(
78+
mainAxisAlignment: MainAxisAlignment.center,
79+
children: <Widget>[
80+
IndexedStack(
81+
index: index,
82+
children: <Widget>[
83+
for (String name in names) PersonTracker(name: name)
84+
],
85+
)
86+
],
87+
),
88+
GestureDetector(
89+
onTap: () {
90+
setState(() {
91+
if (index == names.length - 1) {
92+
index = 0;
93+
} else {
94+
index += 1;
95+
}
96+
});
97+
},
98+
child: const Icon(key: Key('gesture2'), Icons.chevron_right),
99+
),
100+
],
101+
)
102+
],
103+
);
104+
}
105+
}
106+
107+
class PersonTracker extends StatefulWidget {
108+
const PersonTracker({super.key, required this.name});
109+
final String name;
110+
@override
111+
State<PersonTracker> createState() => _PersonTrackerState();
112+
}
113+
114+
class _PersonTrackerState extends State<PersonTracker> {
115+
int counter = 0;
116+
@override
117+
Widget build(BuildContext context) {
118+
return Container(
119+
key: Key(widget.name),
120+
decoration: BoxDecoration(
121+
color: const Color.fromARGB(255, 239, 248, 255),
122+
border: Border.all(color: const Color.fromARGB(255, 54, 60, 244)),
123+
borderRadius: const BorderRadius.all(Radius.circular(10)),
124+
),
125+
padding: const EdgeInsets.all(16.0),
126+
child: Column(
127+
children: <Widget>[
128+
Text('Name: ${widget.name}'),
129+
Text('Score: $counter'),
130+
TextButton.icon(
131+
key: Key('increment${widget.name}'),
132+
icon: const Icon(Icons.add),
133+
onPressed: () {
134+
setState(() {
135+
counter += 1;
136+
});
137+
},
138+
label: const Text('Increment'),
139+
)
140+
],
141+
),
142+
);
143+
}
144+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/widgets/basic/indexed_stack.0.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('has correct forward rendering mechanism', (WidgetTester tester) async {
11+
await tester.pumpWidget(const example.MyApp());
12+
13+
final Finder gesture2 = find.byKey(const Key('gesture2'));
14+
final Element containerFinder = find.byKey(const Key('Dash')).evaluate().first;
15+
expect(containerFinder.renderObject!.debugNeedsPaint, false);
16+
final Element containerFinder1 = find.byKey(const Key('John')).evaluate().first;
17+
expect(containerFinder1.renderObject!.debugNeedsPaint, true);
18+
final Element containerFinder2 = find.byKey(const Key('Mary')).evaluate().first;
19+
expect(containerFinder2.renderObject!.debugNeedsPaint, true);
20+
21+
await tester.tap(gesture2);
22+
await tester.pump();
23+
expect(containerFinder.renderObject!.debugNeedsPaint, false);
24+
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
25+
expect(containerFinder2.renderObject!.debugNeedsPaint, true);
26+
27+
await tester.tap(gesture2);
28+
await tester.pump();
29+
expect(containerFinder.renderObject!.debugNeedsPaint, false);
30+
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
31+
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
32+
});
33+
testWidgets('has correct backward rendering mechanism', (WidgetTester tester) async {
34+
await tester.pumpWidget(const example.MyApp());
35+
36+
final Finder gesture1 = find.byKey(const Key('gesture1'));
37+
final Element containerFinder = find.byKey(const Key('Dash')).evaluate().first;
38+
final Element containerFinder1 = find.byKey(const Key('John')).evaluate().first;
39+
final Element containerFinder2 = find.byKey(const Key('Mary')).evaluate().first;
40+
41+
await tester.tap(gesture1);
42+
await tester.pump();
43+
expect(containerFinder.renderObject!.debugNeedsPaint, false);
44+
expect(containerFinder1.renderObject!.debugNeedsPaint, true);
45+
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
46+
47+
await tester.tap(gesture1);
48+
await tester.pump();
49+
expect(containerFinder.renderObject!.debugNeedsPaint, false);
50+
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
51+
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
52+
});
53+
testWidgets('has correct element addition handling', (WidgetTester tester) async {
54+
await tester.pumpWidget(const example.MyApp());
55+
56+
expect(find.byType(example.PersonTracker), findsNWidgets(3));
57+
final Finder textField = find.byType(TextField);
58+
await tester.enterText(textField, 'hello');
59+
await tester.testTextInput.receiveAction(TextInputAction.done);
60+
await tester.pump();
61+
expect(find.byType(example.PersonTracker), findsNWidgets(4));
62+
63+
await tester.enterText(textField, 'hello1');
64+
await tester.testTextInput.receiveAction(TextInputAction.done);
65+
await tester.pump();
66+
expect(find.byType(example.PersonTracker), findsNWidgets(5));
67+
});
68+
testWidgets('has state preservation', (WidgetTester tester) async {
69+
await tester.pumpWidget(const example.MyApp());
70+
71+
final Finder gesture1 = find.byKey(const Key('gesture1'));
72+
final Finder gesture2 = find.byKey(const Key('gesture2'));
73+
final Finder containerFinder = find.byKey(const Key('Dash'));
74+
final Finder incrementFinder = find.byKey(const Key('incrementDash'));
75+
Finder counterFinder(int score) {
76+
return find.descendant(of: containerFinder, matching: find.text('Score: $score'));
77+
}
78+
79+
expect(counterFinder(0), findsOneWidget);
80+
await tester.tap(incrementFinder);
81+
await tester.pump();
82+
83+
expect(counterFinder(1), findsOneWidget);
84+
85+
await tester.tap(gesture2);
86+
await tester.pump();
87+
await tester.tap(gesture1);
88+
await tester.pump();
89+
90+
expect(counterFinder(1), findsOneWidget);
91+
expect(counterFinder(0), findsNothing);
92+
});
93+
}

packages/flutter/lib/src/widgets/basic.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3846,6 +3846,13 @@ class Stack extends MultiChildRenderObjectWidget {
38463846
///
38473847
/// {@youtube 560 315 https://www.youtube.com/watch?v=_O0PPD1Xfbk}
38483848
///
3849+
/// {@tool dartpad}
3850+
/// This example shows a [IndexedStack] widget being used to lay out one card
3851+
/// at a time from a series of cards, each keeping their respective states.
3852+
///
3853+
/// ** See code in examples/api/lib/widgets/basic/indexed_stack.0.dart **
3854+
/// {@end-tool}
3855+
///
38493856
/// See also:
38503857
///
38513858
/// * [Stack], for more details about stacks.

0 commit comments

Comments
 (0)