connect static method
Connects to Google Drive, authenticating the user.
This method handles the Google Sign-In flow. It will attempt to sign in
silently first, unless forceInteractive
is true.
scopes
a list of additional Google API scopes to request.
The default scopes are drive.DriveApi.driveAppdataScope
or
drive.DriveApi.driveScope
depending on MultiCloudStorage.cloudAccess
.
serverClientId
The server client ID for requesting an ID token if you
need to authenticate to a backend server.
Returns a connected GoogleDriveProvider instance on success, or null on failure/cancellation.
Implementation
static Future<GoogleDriveProvider?> connect(
{bool forceInteractive = false,
List<String>? scopes,
String? serverClientId}) async {
debugPrint("connect Google Drive, forceInteractive: $forceInteractive");
// Return existing instance if already connected and not forcing a new interactive session.
if (_instance != null && _instance!._isAuthenticated && !forceInteractive) {
return _instance;
}
if (scopes != null) {
_scopes = scopes;
}
try {
// Initialize GoogleSignIn with the correct scope based on the desired cloud access level.
_googleSignIn ??=
GoogleSignIn(scopes: _scopes, serverClientId: serverClientId);
GoogleSignInAccount? account;
// Attempt silent sign-in first to avoid unnecessary user interaction.
if (!forceInteractive) {
account = await _googleSignIn!.signInSilently();
}
// If silent sign-in fails or is skipped, start the interactive sign-in flow.
account ??= await _googleSignIn!.signIn();
if (account == null) {
debugPrint('User cancelled Google Sign-In process.');
return null;
}
// Ensure the user has granted the required permissions.
final bool hasPermissions = await _googleSignIn!.requestScopes(_scopes);
if (!hasPermissions) {
debugPrint('User did not grant necessary Google Drive permissions.');
await signOut();
return null;
}
// Get the authenticated HTTP client.
final client = await _googleSignIn!.authenticatedClient();
if (client == null) {
debugPrint(
'Failed to get authenticated Google client after permissions were granted.');
await signOut();
return null;
}
// Wrap the client in a RetryClient to handle transient network errors (5xx).
final retryClient = RetryClient(
client,
retries: 3,
when: (response) => {500, 502, 503, 504}.contains(response.statusCode),
onRetry: (request, response, retryCount) => debugPrint(
'Retrying request to ${request.url} (Retry #$retryCount)'),
);
// Create or update the singleton instance with the authenticated client.
final provider = _instance ?? GoogleDriveProvider._create();
provider.driveApi = drive.DriveApi(retryClient);
provider._isAuthenticated = true;
_instance = provider;
debugPrint(
'Google Drive user signed in: ID=${account.id}, Email=${account.email}');
return _instance;
} on SocketException catch (e) {
debugPrint('No internet connection during Google Drive sign-in.');
throw NoConnectionException(e.message);
} catch (error) {
debugPrint(
'Error occurred during the Google Drive connect process.',
);
if (error is PlatformException && error.code == 'network_error') {
throw NoConnectionException(error.toString());
}
await signOut(); // Clean up on error.
return null;
}
}