Skip to content

Commit 586faa6

Browse files
authored
[google_sign_in_web] Update button_tester to use web_only library. (flutter#6868)
(Cleanup) While reviewing a separate issue (flutter#149236), I noticed that the `button_tester` example app hadn't been updated to the latest style of using the `web_only` library to "renderButton". This PR updates the implementation of the `button_tester` example app to use the `web_only.dart` library from `package:google_sign_in_web`, instead of attempting to access the web-only methods through funky casts. While I was there, I also changed a couple of things in the selectable options column: * (Usability) Ensured the "default" value of each option is rendered by default in the option selection as well. * (Style) Refactored the column of cards to be a `ListView.builder`, rather than a `SingleChildScrollView + Column`. ## Testing I haven't deployed this anywhere, but this is what it looks like: <img width="821" alt="Screenshot 2024-06-04 at 7 39 13�PM" src="https://github.com/flutter/packages/assets/1255594/284ba951-1d46-4ffb-9136-102344337286"> ## Versioning This change doesn't need publishing/versioning; it's purely reference code living in the repo.
1 parent 023210f commit 586faa6

File tree

2 files changed

+165
-133
lines changed

2 files changed

+165
-133
lines changed

packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart

+9-9
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44

55
import 'package:flutter/material.dart';
66
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
7-
import 'package:google_sign_in_web/google_sign_in_web.dart';
7+
import 'package:google_sign_in_web/web_only.dart';
88

99
import 'src/button_configuration_column.dart';
1010

11-
// The instance of the plugin is automatically created by Flutter before calling
12-
// our main code, let's grab it directly from the Platform interface of the plugin.
13-
final GoogleSignInPlugin _plugin =
14-
GoogleSignInPlatform.instance as GoogleSignInPlugin;
11+
// Let's use the Platform Interface directly, no need to use anything web-specific
12+
// from it. (In a normal app, we'd use the plugin interface!)
13+
// All the web-specific imports come from the `web_only.dart` library.
14+
final GoogleSignInPlatform _platform = GoogleSignInPlatform.instance;
1515

1616
Future<void> main() async {
17-
await _plugin.initWithParams(const SignInInitParameters(
17+
await _platform.initWithParams(const SignInInitParameters(
1818
clientId: 'your-client_id.apps.googleusercontent.com',
1919
));
2020
runApp(
@@ -41,15 +41,15 @@ class _ButtonConfiguratorState extends State<ButtonConfiguratorDemo> {
4141
@override
4242
void initState() {
4343
super.initState();
44-
_plugin.userDataEvents?.listen((GoogleSignInUserData? userData) {
44+
_platform.userDataEvents?.listen((GoogleSignInUserData? userData) {
4545
setState(() {
4646
_userData = userData;
4747
});
4848
});
4949
}
5050

5151
void _handleSignOut() {
52-
_plugin.signOut();
52+
_platform.signOut();
5353
setState(() {
5454
// signOut does not broadcast through the userDataEvents, so we fake it.
5555
_userData = null;
@@ -70,7 +70,7 @@ class _ButtonConfiguratorState extends State<ButtonConfiguratorDemo> {
7070
mainAxisAlignment: MainAxisAlignment.center,
7171
children: <Widget>[
7272
if (_userData == null)
73-
_plugin.renderButton(configuration: _buttonConfiguration),
73+
renderButton(configuration: _buttonConfiguration),
7474
if (_userData != null) ...<Widget>[
7575
Text('Hello, ${_userData!.displayName}!'),
7676
ElevatedButton(

packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart

+156-124
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/material.dart';
6-
import 'package:google_sign_in_web/google_sign_in_web.dart';
6+
import 'package:google_sign_in_web/web_only.dart';
77

88
/// Type of the onChange function for `ButtonConfiguration`.
99
typedef OnWebConfigChangeFn = void Function(GSIButtonConfiguration newConfig);
1010

11-
/// (Incomplete) List of the locales that can be used to configure the button.
12-
const List<String> availableLocales = <String>[
11+
/// The type of the widget builder function for each Card in the ListView builder
12+
typedef CardBuilder = Widget Function(
13+
GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange);
14+
15+
// (Incomplete) List of the locales that can be used to configure the button.
16+
const List<String> _availableLocales = <String>[
1317
'en_US',
1418
'es_ES',
1519
'pt_BR',
@@ -18,123 +22,146 @@ const List<String> availableLocales = <String>[
1822
'de_DE',
1923
];
2024

25+
// The builder functions for the Cards that let users customize the button.
26+
final List<CardBuilder> _cards = <CardBuilder>[
27+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
28+
_renderLocaleCard(
29+
value: currentConfig?.locale ?? 'en_US',
30+
locales: _availableLocales,
31+
onChanged: _onChanged<String>(currentConfig, onChange),
32+
),
33+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
34+
_renderMinimumWidthCard(
35+
value: currentConfig?.minimumWidth,
36+
max: 500,
37+
actualMax: 400,
38+
onChanged: _onChanged<double>(currentConfig, onChange),
39+
),
40+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
41+
_renderRadioListTileCard<GSIButtonType>(
42+
title: 'ButtonType',
43+
values: GSIButtonType.values,
44+
selected: currentConfig?.type ?? GSIButtonType.standard,
45+
onChanged: _onChanged<GSIButtonType>(currentConfig, onChange),
46+
),
47+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
48+
_renderRadioListTileCard<GSIButtonShape>(
49+
title: 'ButtonShape',
50+
values: GSIButtonShape.values,
51+
selected: currentConfig?.shape ?? GSIButtonShape.rectangular,
52+
onChanged: _onChanged<GSIButtonShape>(currentConfig, onChange),
53+
),
54+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
55+
_renderRadioListTileCard<GSIButtonSize>(
56+
title: 'ButtonSize',
57+
values: GSIButtonSize.values,
58+
selected: currentConfig?.size ?? GSIButtonSize.large,
59+
onChanged: _onChanged<GSIButtonSize>(currentConfig, onChange),
60+
),
61+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
62+
_renderRadioListTileCard<GSIButtonTheme>(
63+
title: 'ButtonTheme',
64+
values: GSIButtonTheme.values,
65+
selected: currentConfig?.theme ?? GSIButtonTheme.outline,
66+
onChanged: _onChanged<GSIButtonTheme>(currentConfig, onChange),
67+
),
68+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
69+
_renderRadioListTileCard<GSIButtonText>(
70+
title: 'ButtonText',
71+
values: GSIButtonText.values,
72+
selected: currentConfig?.text ?? GSIButtonText.signinWith,
73+
onChanged: _onChanged<GSIButtonText>(currentConfig, onChange),
74+
),
75+
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
76+
_renderRadioListTileCard<GSIButtonLogoAlignment>(
77+
title: 'ButtonLogoAlignment',
78+
values: GSIButtonLogoAlignment.values,
79+
selected: currentConfig?.logoAlignment ?? GSIButtonLogoAlignment.left,
80+
onChanged: _onChanged<GSIButtonLogoAlignment>(currentConfig, onChange),
81+
),
82+
];
83+
2184
/// Renders a Scrollable Column widget that allows the user to see (and change) a ButtonConfiguration.
2285
Widget renderWebButtonConfiguration(
2386
GSIButtonConfiguration? currentConfig, {
2487
OnWebConfigChangeFn? onChange,
2588
}) {
2689
final ScrollController scrollController = ScrollController();
2790
return Scrollbar(
28-
controller: scrollController,
29-
thumbVisibility: true,
30-
interactive: true,
31-
child: SingleChildScrollView(
32-
controller: scrollController,
33-
child: Column(
34-
crossAxisAlignment: CrossAxisAlignment.start,
35-
children: <Widget>[
36-
_renderLocaleCard(
37-
value: currentConfig?.locale,
38-
locales: availableLocales,
39-
onChanged: _onChanged<String>(currentConfig, onChange),
40-
),
41-
_renderMinimumWidthCard(
42-
value: currentConfig?.minimumWidth,
43-
max: 500,
44-
actualMax: 400,
45-
onChanged: _onChanged<double>(currentConfig, onChange),
46-
),
47-
_renderRadioListTileCard<GSIButtonType>(
48-
title: 'ButtonType',
49-
values: GSIButtonType.values,
50-
selected: currentConfig?.type,
51-
onChanged: _onChanged<GSIButtonType>(currentConfig, onChange),
52-
),
53-
_renderRadioListTileCard<GSIButtonShape>(
54-
title: 'ButtonShape',
55-
values: GSIButtonShape.values,
56-
selected: currentConfig?.shape,
57-
onChanged: _onChanged<GSIButtonShape>(currentConfig, onChange),
58-
),
59-
_renderRadioListTileCard<GSIButtonSize>(
60-
title: 'ButtonSize',
61-
values: GSIButtonSize.values,
62-
selected: currentConfig?.size,
63-
onChanged: _onChanged<GSIButtonSize>(currentConfig, onChange),
64-
),
65-
_renderRadioListTileCard<GSIButtonTheme>(
66-
title: 'ButtonTheme',
67-
values: GSIButtonTheme.values,
68-
selected: currentConfig?.theme,
69-
onChanged: _onChanged<GSIButtonTheme>(currentConfig, onChange),
70-
),
71-
_renderRadioListTileCard<GSIButtonText>(
72-
title: 'ButtonText',
73-
values: GSIButtonText.values,
74-
selected: currentConfig?.text,
75-
onChanged: _onChanged<GSIButtonText>(currentConfig, onChange),
76-
),
77-
_renderRadioListTileCard<GSIButtonLogoAlignment>(
78-
title: 'ButtonLogoAlignment',
79-
values: GSIButtonLogoAlignment.values,
80-
selected: currentConfig?.logoAlignment,
81-
onChanged:
82-
_onChanged<GSIButtonLogoAlignment>(currentConfig, onChange),
83-
),
84-
],
85-
)));
91+
controller: scrollController,
92+
thumbVisibility: true,
93+
interactive: true,
94+
child: ConstrainedBox(
95+
constraints: const BoxConstraints(maxWidth: 250),
96+
child: ListView.builder(
97+
controller: scrollController,
98+
itemCount: _cards.length,
99+
itemBuilder: (BuildContext _, int index) =>
100+
_cards[index](currentConfig, onChange),
101+
),
102+
),
103+
);
86104
}
87105

88106
/// Renders a Config card with a dropdown of locales.
89-
Widget _renderLocaleCard(
90-
{String? value,
91-
required List<String> locales,
92-
void Function(String?)? onChanged}) {
93-
return _renderConfigCard(title: 'locale', children: <Widget>[
94-
Padding(
95-
padding: const EdgeInsets.symmetric(horizontal: 16),
96-
child: DropdownButton<String>(
97-
items: locales
98-
.map((String locale) => DropdownMenuItem<String>(
99-
value: locale,
100-
child: Text(locale),
101-
))
102-
.toList(),
103-
value: value,
104-
onChanged: onChanged,
105-
isExpanded: true,
106-
// padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here!
107+
Widget _renderLocaleCard({
108+
String? value,
109+
required List<String> locales,
110+
void Function(String?)? onChanged,
111+
}) {
112+
return _renderConfigCard(
113+
title: 'locale',
114+
children: <Widget>[
115+
Padding(
116+
padding: const EdgeInsets.symmetric(horizontal: 16),
117+
child: DropdownButton<String>(
118+
items: locales
119+
.map((String locale) => DropdownMenuItem<String>(
120+
value: locale,
121+
child: Text(locale),
122+
))
123+
.toList(),
124+
value: value,
125+
onChanged: onChanged,
126+
isExpanded: true,
127+
// padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here!
128+
),
107129
),
108-
),
109-
]);
130+
],
131+
);
110132
}
111133

112134
/// Renders a card with a slider
113-
Widget _renderMinimumWidthCard(
114-
{double? value,
115-
double min = 0,
116-
double actualMax = 10,
117-
double max = 11,
118-
void Function(double)? onChanged}) {
119-
return _renderConfigCard(title: 'minimumWidth', children: <Widget>[
120-
Slider(
121-
label: value?.toString() ?? 'null',
122-
value: value ?? 0,
123-
min: min,
124-
max: max,
125-
secondaryTrackValue: actualMax,
126-
onChanged: onChanged,
127-
divisions: 10,
128-
)
129-
]);
135+
Widget _renderMinimumWidthCard({
136+
double? value,
137+
double min = 0,
138+
double actualMax = 10,
139+
double max = 11,
140+
void Function(double)? onChanged,
141+
}) {
142+
return _renderConfigCard(
143+
title: 'minimumWidth',
144+
children: <Widget>[
145+
Slider(
146+
label: value?.toString() ?? 'null',
147+
value: value ?? 0,
148+
min: min,
149+
max: max,
150+
secondaryTrackValue: actualMax,
151+
onChanged: onChanged,
152+
divisions: 10,
153+
)
154+
],
155+
);
130156
}
131157

132158
/// Renders a Config Card with the values of an Enum as radio buttons.
133-
Widget _renderRadioListTileCard<T extends Enum>(
134-
{required String title,
135-
required List<T> values,
136-
T? selected,
137-
void Function(T?)? onChanged}) {
159+
Widget _renderRadioListTileCard<T extends Enum>({
160+
required String title,
161+
required List<T> values,
162+
T? selected,
163+
void Function(T?)? onChanged,
164+
}) {
138165
return _renderConfigCard(
139166
title: title,
140167
children: values
@@ -150,29 +177,32 @@ Widget _renderRadioListTileCard<T extends Enum>(
150177
}
151178

152179
/// Renders a Card where we render some `children` that change config.
153-
Widget _renderConfigCard(
154-
{required String title, required List<Widget> children}) {
155-
return Container(
156-
constraints: const BoxConstraints(maxWidth: 200),
157-
child: Card(
158-
child: Column(
159-
mainAxisSize: MainAxisSize.min,
160-
children: <Widget>[
161-
ListTile(
162-
title: Text(
163-
title,
164-
style: const TextStyle(fontWeight: FontWeight.bold),
165-
),
166-
dense: true,
180+
Widget _renderConfigCard({
181+
required String title,
182+
required List<Widget> children,
183+
}) {
184+
return Card(
185+
child: Column(
186+
mainAxisSize: MainAxisSize.min,
187+
children: <Widget>[
188+
ListTile(
189+
title: Text(
190+
title,
191+
style: const TextStyle(fontWeight: FontWeight.bold),
167192
),
168-
...children,
169-
],
170-
)));
193+
dense: true,
194+
),
195+
...children,
196+
],
197+
),
198+
);
171199
}
172200

173201
/// Sets a `value` into an `old` configuration object.
174-
GSIButtonConfiguration _copyConfigWith(
175-
GSIButtonConfiguration? old, Object? value) {
202+
GSIButtonConfiguration _copyConfigWith<T>(
203+
GSIButtonConfiguration? old,
204+
T? value,
205+
) {
176206
return GSIButtonConfiguration(
177207
locale: value is String ? value : old?.locale,
178208
minimumWidth:
@@ -188,11 +218,13 @@ GSIButtonConfiguration _copyConfigWith(
188218

189219
/// Returns a function that modifies the `current` configuration with a `value`, then calls `fn` with it.
190220
void Function(T?)? _onChanged<T>(
191-
GSIButtonConfiguration? current, OnWebConfigChangeFn? fn) {
221+
GSIButtonConfiguration? current,
222+
OnWebConfigChangeFn? fn,
223+
) {
192224
if (fn == null) {
193225
return null;
194226
}
195227
return (T? value) {
196-
fn(_copyConfigWith(current, value));
228+
fn(_copyConfigWith<T>(current, value));
197229
};
198230
}

0 commit comments

Comments
 (0)