I am currently encountering a problem where my google_sign_in button rendered from the Web is twitching as shown here:
GIF: GOOGLE SIGN IN WIDGET TWITCHING
I have followed the example given from the pub dev https://pub.dev/packages/google_sign_in and have achieved success in sign in and sign out, the functionality works, it's just that the widget keeps on twitching and it doesn't look great at all.
I wonder if I was doing something obviously wrong, because I could not find anyone else having the same problem as me anywhere. So here's my Flutter widget code that should be quite straight forward:
import 'dart:async';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_sign_in_web/web_only.dart' as web;
import 'package:pos_billing/constants/env.dart';
import 'package:pos_billing/pages/authentication/helper/auth_state.dart';
import 'package:provider/provider.dart';
import 'package:pos_billing/pages/widgets/default/custom_toast.dart';
class GoogleSignInButton extends StatefulWidget {
const GoogleSignInButton({super.key});
@override
State<GoogleSignInButton> createState() => _GoogleSignInButtonState();
}
class _GoogleSignInButtonState extends State<GoogleSignInButton> {
final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
StreamSubscription<GoogleSignInAuthenticationEvent>? _authSub;
bool _handlingAuth = false;
bool _loading = false;
// initializing google sign in
@override
void initState() {
super.initState();
_googleSignIn.initialize(clientId: EnvConfig.googleClientId).then((
_,
) async {
_authSub = _googleSignIn.authenticationEvents.listen(
_handleAuthenticationEvent,
onError: _handleAuthenticationError,
);
_googleSignIn.attemptLightweightAuthentication();
});
}
@override
void dispose() {
_authSub?.cancel();
super.dispose();
}
// listens when authentication event occurs
Future<void> _handleAuthenticationEvent(
GoogleSignInAuthenticationEvent event,
) async {
if (_handlingAuth) {
return;
} else {
setState(() => _handlingAuth = true);
}
final GoogleSignInAccount? user = switch (event) {
GoogleSignInAuthenticationEventSignIn() => event.user,
GoogleSignInAuthenticationEventSignOut() => null,
};
if (user != null) {
try {
final idToken = user.authentication.idToken;
final auth = context.read<AuthModel>();
// API callback to authenticate in backend
await auth.loginGoogle(idToken!);
if (!mounted) return;
await auth.onLoginCallback(context, user.email);
} catch (e) {
_onAuthError(e);
}
setState(() => _handlingAuth = false);
}
}
// handles auth errors
Future<void> _handleAuthenticationError(Object e) async {
await context.read<AuthModel>().logout();
if (!mounted) return;
String? message = 'Unknown error: $e';
if (e is GoogleSignInException) {
if (e.code.name == 'canceled') return;
message = e.code.name;
}
CustomToast.error(title: message);
}
Future<void> _authenticate() async {
if (!_googleSignIn.supportsAuthenticate()) return;
setState(() => _loading = true);
try {
await _googleSignIn.authenticate();
} catch (e) {
_onAuthError(e);
} finally {
if (mounted) setState(() => _loading = false);
}
}
void _onAuthError(Object error) {
if (!mounted) return;
String msg = '';
if (error is GoogleSignInException) {
msg = switch (error.code) {
GoogleSignInExceptionCode.canceled => 'Sign in canceled',
_ => 'GoogleSignInException ${error.code}: ${error.description}',
};
}
msg = 'Sign in failed: $error';
CustomToast.error(title: msg);
}
@override
Widget build(BuildContext context) {
if (kIsWeb && !_googleSignIn.supportsAuthenticate()) {
return web.renderButton(
configuration: web.GSIButtonConfiguration(type: web.GSIButtonType.icon),
);
}
return Material(
child: InkWell(
onTap: _loading ? null : _authenticate,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFDADCE0)),
borderRadius: BorderRadius.circular(4),
),
child: SvgPicture.asset(
'lib/assets/google_logo.svg',
width: 18,
height: 18,
),
),
),
);
}
}
So my question is why does this happen and how can I solve this twitching widget issue, is it a bug from the package? Thank you for your time!
After reviewing my code, I realize that I had an unused parent widget which was causing the issue.
GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
...
// GoogleSignInButton() is placed as a children of GestureDetector
...
)
By removing the GestureDetector, it no longer rebuilds on tapping/unfocus, thus the twitching is gone! Thank you @Niyam Prassanna Kunder for the heads up.