[google_sign_in_web] Fixes force unwrap on values that can be null (#4374)

During Google Sign-in, the code uses two force unwraps on values (name and picture) that can be not present in the response. This cause an unhandled error that blocks sign-in.

Fixes https://github.com/flutter/flutter/issues/130002 reported by me. The bug report describes how to get that error together with a screenshot of a given line.

My PR fixes that and add additional test for the future.
diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
index 363226b..7d39cd6 100644
--- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.12.0+3
+
+* Fixes null cast error on accounts without picture or name details.
+
 ## 0.12.0+2
 
 * Adds compatibility with `http` 1.0.
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart
index 72841c5..336b626 100644
--- a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart
@@ -18,6 +18,12 @@
   'credential': goodJwtToken,
 });
 
+/// A CredentialResponse wrapping a known good JWT Token as its `credential`.
+final CredentialResponse minimalCredential =
+    jsifyAs<CredentialResponse>(<String, Object?>{
+  'credential': minimalJwtToken,
+});
+
 /// A JWT token with predefined values.
 ///
 /// 'email': 'adultman@example.com',
@@ -38,6 +44,22 @@
 const String goodPayload =
     'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwibmFtZSI6IlZpbmNlbnQgQWR1bHRtYW4iLCJwaWN0dXJlIjoiaHR0cHM6Ly90aGlzcGVyc29uZG9lc25vdGV4aXN0LmNvbS9pbWFnZT94PS5qcGcifQ';
 
+/// A JWT token with minimal set of predefined values.
+///
+/// 'email': 'adultman@example.com',
+/// 'sub': '123456'
+///
+/// Signed with HS256 and the private key: 'symmetric-encryption-is-weak'
+const String minimalJwtToken =
+    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.$minimalPayload.UTAe7dpdtFIMwsOqkZkjyjqyHnho5xHCcQylUFmOutM';
+
+/// The payload of a JWT token that contains only non-nullable values.
+///
+/// "email": "adultman@example.com",
+/// "sub": "123456"
+const String minimalPayload =
+    'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2In0';
+
 // More encrypted JWT Tokens may be created on https://jwt.io.
 //
 // First, decode the `goodJwtToken` above, modify to your heart's
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart
index 82701e5..6f5fcfd 100644
--- a/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart
@@ -57,6 +57,17 @@
       expect(data.idToken, goodJwtToken);
     });
 
+    testWidgets('happy case (minimal)', (_) async {
+      final GoogleSignInUserData data =
+          gisResponsesToUserData(minimalCredential)!;
+
+      expect(data.displayName, isNull);
+      expect(data.id, '123456');
+      expect(data.email, 'adultman@example.com');
+      expect(data.photoUrl, isNull);
+      expect(data.idToken, minimalJwtToken);
+    });
+
     testWidgets('null response -> null', (_) async {
       expect(gisResponsesToUserData(null), isNull);
     });
@@ -90,6 +101,14 @@
           ));
     });
 
+    testWidgets('happy case (minimal) -> data', (_) async {
+      final Map<String, Object?>? data = getJwtTokenPayload(minimalJwtToken);
+
+      expect(data, isNotNull);
+      expect(data, containsPair('email', 'adultman@example.com'));
+      expect(data, containsPair('sub', '123456'));
+    });
+
     testWidgets('null Token -> null', (_) async {
       final Map<String, Object?>? data = getJwtTokenPayload(null);
 
diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart
index c4bb9d4..8ec9cb5 100644
--- a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart
+++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart
@@ -72,8 +72,8 @@
   return GoogleSignInUserData(
     email: payload['email']! as String,
     id: payload['sub']! as String,
-    displayName: payload['name']! as String,
-    photoUrl: payload['picture']! as String,
+    displayName: payload['name'] as String?,
+    photoUrl: payload['picture'] as String?,
     idToken: credentialResponse.credential,
   );
 }
diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
index 9a2847f..0accb27 100644
--- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
@@ -3,7 +3,7 @@
   for signing in with a Google account on Android, iOS and Web.
 repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 0.12.0+2
+version: 0.12.0+3
 
 environment:
   sdk: ">=2.18.0 <4.0.0"