Google Sign-in for Flutter Web
My Flutter app was running fine for Web until I migrated google_sign_in from 6 to 7. Now signing-in fails with this error: Sign in failed: UnimplementedError: authenticate is not supported on the web. Instead, use renderButton to create a sign-in widget. Let's fix it.
Up until google_sign_in 6, it seemed like I could use the same code to sign in on iOS and Web. But from version 7, authentication on Web is different.
On the web, instead of providing custom UI that calls authenticate, you should display the Widget returned byrenderButton(from web_only.dart), and listen toauthenticationEventsto know when the user has signed in.
Adding the button
I changed my code to render that special button on Web:
Before:
ElevatedButton(
child: Text('Sign in'),
onPressed: () {
_handleSignIn(widget.auth, widget.googleSignIn);
})After:
!kIsWeb
? ElevatedButton(
child: Text('Sign in'),
onPressed: () {
_handleSignIn(widget.auth, widget.googleSignIn);
})
: renderButton()
Adding the dependency
However, VSC couldn't find the renderButton. https://pub.dev/packages/google_sign_in_web has the explanation why:
This package is endorsed, which means you can simply use google_sign_in normally. This package will be automatically included in your app when you do, so you do not need to add it to your pubspec.yaml. However, if you import this package to use any of its APIs directly, you should add it to your pubspec.yaml as usual. For example, you need to import this package directly if you plan to use the web-only Widget renderButton() method.
So I do need to manually import this package.
init()
After I add the renderButton, I am getting the following error:
Bad state: GoogleSignInPlugin::init() or GoogleSignInPlugin::initWithParams() must be called before any other method in this plugin.
Looking back at https://pub.dev/packages/google_sign_in#usage, I have all the information I need to initialize it in code:
unawaited(_googleSignIn.initialize(
clientId:
'....apps.googleusercontent.com'));Back in version 6, I had two choices to initialize GoogleSignIn. I could either pass the client id in code, or add a <meta> tag. I guess the meta tag way does not work anymore.
Running with a fixed port
If I attempt to run in Chrome using VSC's built-in Play button, the web app runs on a random port every time. Then Google Sign-In will throw an Access blocked: Authorization Error, Error 400: origin_mismatch.
So I need to run the exact port that I whitelisted on the Cloud Console:
flutter run -d chrome --web-port 5000After setting it all up, Google Sign-In works on Web again! But wait a second...
Restoring compilation for iOS
Adding a dependency to google_sign_in_web actually broke compilation on other platforms like iOS. When I try to run the app on iOS, I get those errors:
../../.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/webrtc.dart:1067:40: Error: 'JSObject' isn't a type.
extension type RTCRtpTransceiverInit._(JSObject _) implements JSObject {
^^^^^^^^
../../.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/webrtc.dart:1070:5: Error: 'JSArray' isn't a type.
JSArray<MediaStream> streams,
^^^^^^^
../../.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/webrtc.dart:1071:5: Error: 'JSArray' isn't a type.
JSArray<RTCRtpEncodingParameters> sendEncodings,
^^^^^^^
...
../../.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib/src/js_interop/google_accounts_oauth2.dart:79:51: Error: The getter 'toJS' isn't defined for the
type 'void Function(TokenRevocationResponse)'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'toJS'.
return _revokeWithDone(accessToken.toJS, done.toJS);
^^^^
../../.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib/src/js_interop/google_accounts_oauth2.dart:83:25: Error: 'JSString' isn't a type.
external void _revoke(JSString accessToken);
^^^^^^^^
../../.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib/src/js_interop/google_accounts_oauth2.dart:85:33: Error: 'JSString' isn't a type.
external void _revokeWithDone(JSString accessToken, JSFunction done);
^^^^^^^^Luckily Gemini has a good way to isolate the code.
import 'package:flutter/material.dart';
import 'package:google_sign_in_web/web_only.dart';
class SignInButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return renderButton();
}
}import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:provider/provider.dart';
class SignInButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final googleSignIn = context.read<GoogleSignIn>();
return ElevatedButton(
child: Text('Sign in'),
onPressed: () {
googleSignIn.authenticate();
});
}
}// Default export for mobile/desktop
export 'sign_in_button_not_web.dart'
// Conditional export for web
if (dart.library.js_interop) 'sign_in_button_web.dart';import 'package:elysium/sign_in_button.dart';
Provider(
create: (context) => widget.googleSignIn,
child: SignInButton())And indeed, it compiled fine:
Running Xcode build...
Xcode build done. 40.6s
✓ Built build/ios/iphoneos/Runner.app (50.1MB)Conditional initialize
When I click on Sign in, I now get an error about not supporting the right URL. That's because I initialized GoogleSignIn with the key used for Web, whereas I configured my iOS app to use other keys.
Before:
unawaited(_googleSignIn.initialize(
clientId:
'[...].apps.googleusercontent.com'));After:
unawaited(_googleSignIn.initialize(
clientId: kIsWeb
? '[...].apps.googleusercontent.com'
: null));