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
5import 'dart:ui';
6
7import 'package:flutter/services.dart';
8import 'package:flutter/widgets.dart';
9import 'package:flutter_test/flutter_test.dart';
10import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
11
12void main() {
13 AppLifecycleListener? listener;
14
15 Future<void> setAppLifeCycleState(AppLifecycleState state) async {
16 final ByteData? message = const StringCodec().encodeMessage(state.toString());
17 await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
18 'flutter/lifecycle',
19 message,
20 (_) {},
21 );
22 }
23
24 Future<void> sendAppExitRequest() async {
25 final ByteData message = const JSONMethodCodec().encodeMethodCall(
26 const MethodCall('System.requestAppExit'),
27 );
28 await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
29 'flutter/platform',
30 message,
31 (_) {},
32 );
33 }
34
35 setUp(() async {
36 WidgetsFlutterBinding.ensureInitialized();
37 WidgetsBinding.instance
38 ..resetEpoch()
39 ..platformDispatcher.onBeginFrame = null
40 ..platformDispatcher.onDrawFrame = null;
41 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
42 binding.readTestInitialLifecycleStateFromNativeWindow();
43 // Reset the state to detached. Going to paused first makes it a valid
44 // transition from any state, since the intermediate transitions will be
45 // generated.
46 await setAppLifeCycleState(AppLifecycleState.paused);
47 await setAppLifeCycleState(AppLifecycleState.detached);
48 });
49
50 tearDown(() {
51 listener?.dispose();
52 listener = null;
53 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
54 binding.resetInternalState();
55 binding.platformDispatcher.resetInitialLifecycleState();
56 assert(
57 TestAppLifecycleListener.registerCount == 0,
58 'There were ${TestAppLifecycleListener.registerCount} listeners that were not disposed of in tests.',
59 );
60 });
61
62 testWidgets('Default Diagnostics', (WidgetTester tester) async {
63 listener = TestAppLifecycleListener(binding: tester.binding);
64 expect(
65 listener.toString(),
66 equalsIgnoringHashCodes(
67 'TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>)',
68 ),
69 );
70 });
71
72 testWidgets('Diagnostics', (WidgetTester tester) async {
73 Future<AppExitResponse> handleExitRequested() async {
74 return AppExitResponse.cancel;
75 }
76
77 listener = TestAppLifecycleListener(
78 binding: WidgetsBinding.instance,
79 onExitRequested: handleExitRequested,
80 onStateChange: (AppLifecycleState _) {},
81 );
82 expect(
83 listener.toString(),
84 equalsIgnoringHashCodes(
85 'TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>, onStateChange, onExitRequested)',
86 ),
87 );
88 });
89
90 testWidgets('listens to AppLifecycleState', (WidgetTester tester) async {
91 final List<AppLifecycleState> states = <AppLifecycleState>[tester.binding.lifecycleState!];
92 void stateChange(AppLifecycleState state) {
93 states.add(state);
94 }
95
96 listener = TestAppLifecycleListener(
97 binding: WidgetsBinding.instance,
98 onStateChange: stateChange,
99 );
100 expect(states, equals(<AppLifecycleState>[AppLifecycleState.detached]));
101 await setAppLifeCycleState(AppLifecycleState.inactive);
102 // "resumed" is generated.
103 expect(
104 states,
105 equals(<AppLifecycleState>[
106 AppLifecycleState.detached,
107 AppLifecycleState.resumed,
108 AppLifecycleState.inactive,
109 ]),
110 );
111 await setAppLifeCycleState(AppLifecycleState.resumed);
112 expect(
113 states,
114 equals(<AppLifecycleState>[
115 AppLifecycleState.detached,
116 AppLifecycleState.resumed,
117 AppLifecycleState.inactive,
118 AppLifecycleState.resumed,
119 ]),
120 );
121 });
122
123 testWidgets('Triggers correct state transition callbacks', (WidgetTester tester) async {
124 final List<String> transitions = <String>[];
125 listener = TestAppLifecycleListener(
126 binding: WidgetsBinding.instance,
127 onDetach: () => transitions.add('detach'),
128 onHide: () => transitions.add('hide'),
129 onInactive: () => transitions.add('inactive'),
130 onPause: () => transitions.add('pause'),
131 onRestart: () => transitions.add('restart'),
132 onResume: () => transitions.add('resume'),
133 onShow: () => transitions.add('show'),
134 );
135
136 // Try "standard" sequence
137 await setAppLifeCycleState(AppLifecycleState.resumed);
138 expect(transitions, equals(<String>['resume']));
139 await setAppLifeCycleState(AppLifecycleState.inactive);
140 expect(transitions, equals(<String>['resume', 'inactive']));
141 await setAppLifeCycleState(AppLifecycleState.hidden);
142 expect(transitions, equals(<String>['resume', 'inactive', 'hide']));
143 await setAppLifeCycleState(AppLifecycleState.paused);
144 expect(transitions, equals(<String>['resume', 'inactive', 'hide', 'pause']));
145
146 // Go back to resume
147 transitions.clear();
148 await setAppLifeCycleState(AppLifecycleState.hidden);
149 expect(transitions, equals(<String>['restart']));
150 await setAppLifeCycleState(AppLifecycleState.inactive);
151 expect(transitions, equals(<String>['restart', 'show']));
152 await setAppLifeCycleState(AppLifecycleState.resumed);
153 expect(transitions, equals(<String>['restart', 'show', 'resume']));
154
155 // Generates intermediate states from lower to higher lifecycle states.
156 transitions.clear();
157 await setAppLifeCycleState(AppLifecycleState.paused);
158 expect(transitions, equals(<String>['inactive', 'hide', 'pause']));
159
160 // Wraps around from pause to detach.
161 await setAppLifeCycleState(AppLifecycleState.detached);
162 expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach']));
163 await setAppLifeCycleState(AppLifecycleState.resumed);
164 expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach', 'resume']));
165 await setAppLifeCycleState(AppLifecycleState.paused);
166 expect(
167 transitions,
168 equals(<String>[
169 'inactive',
170 'hide',
171 'pause',
172 'detach',
173 'resume',
174 'inactive',
175 'hide',
176 'pause',
177 ]),
178 );
179
180 // Generates intermediate states from higher to lower lifecycle states.
181 transitions.clear();
182 await setAppLifeCycleState(AppLifecycleState.resumed);
183 expect(transitions, equals(<String>['restart', 'show', 'resume']));
184
185 // Go to detached
186 transitions.clear();
187 await setAppLifeCycleState(AppLifecycleState.detached);
188 expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach']));
189 });
190
191 testWidgets('Receives exit requests', (WidgetTester tester) async {
192 bool exitRequested = false;
193 Future<AppExitResponse> handleExitRequested() async {
194 exitRequested = true;
195 return AppExitResponse.cancel;
196 }
197
198 listener = TestAppLifecycleListener(
199 binding: WidgetsBinding.instance,
200 onExitRequested: handleExitRequested,
201 );
202 await sendAppExitRequest();
203 expect(exitRequested, isTrue);
204 });
205
206 test('AppLifecycleListener dispatches memory events', () async {
207 await expectLater(
208 await memoryEvents(
209 () => AppLifecycleListener(binding: WidgetsBinding.instance).dispose(),
210 AppLifecycleListener,
211 ),
212 areCreateAndDispose,
213 );
214 });
215}
216
217class TestAppLifecycleListener extends AppLifecycleListener {
218 TestAppLifecycleListener({
219 super.binding,
220 super.onResume,
221 super.onInactive,
222 super.onHide,
223 super.onShow,
224 super.onPause,
225 super.onRestart,
226 super.onDetach,
227 super.onExitRequested,
228 super.onStateChange,
229 }) {
230 registerCount += 1;
231 }
232
233 static int registerCount = 0;
234
235 @override
236 void dispose() {
237 super.dispose();
238 registerCount -= 1;
239 }
240}
241

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com