Secure the user data on Google Drive with Flutter

Oz Ben
4 min readOct 15, 2023

Google Drive and iCloud are two of the most popular cloud storage services, and they offer a variety of features that can be useful for Flutter developers. For example, Google Drive offers features like real-time collaboration and file sharing, while iCloud offers features a like device backup and sync.

When using those services the developer would able to save mandatory files for the application such as configuration files and keys, at the application data folder.
The application data folder is a special folder that can be by the app to store specific data.
This folder is only accessible by your application and its contents are hidden from the user and from other Drive apps.

Please note: In this article i will cover the Google Drive service usage, the iCloud service is being covered on the following article.

we would be using the googleapis and the googleapis_auth packages.

Step 1: Import the relevant packages

googleapis_auth: ^1.4.1
googleapis: ^11.4.0
path_provider: ^2.1.1

Step 2: Login and authenticate with Google

// Use DriveApi.driveFileScope
final googleSignIn =
GoogleSignIn.standard(scopes: [DriveApi.driveFileScope]);
final GoogleSignInAuthentication? auth =
await googleSignIn.currentUser?.authentication;
final String? accessToken = auth?.accessToken;

Please note:
This article doesn’t discuss the authentication process with Google .
Therefore, one shall add the necessary implementation for it in order to be authenticated.

Step 3: The Google Drive Client

The GoogleDriveClient is a class that contains the interaction functionalities with the Google Drive api.
The api needs an access token that is provided when authenticating with Google at the previous step.

Lets define a helper class that represents an Http client which would carry the REST api calls with the needed headers:

class AuthClient extends http.BaseClient {
final http.Client _baseClient;
final Map<String, String> _headers;

Now we can start implementing the GoogleDriveClient class:

Lets define a create factory method that would initialize the client with the GoogleDrive SDK:

GoogleDriveClient._create(
GoogleSignInAccount googleAccount, String accessToken) {
_googleAccount = googleAccount;
_accessToken = accessToken;
}

static Future<GoogleDriveClient> create(
GoogleSignInAccount googleAccount, String accessToken) async {
var component = GoogleDriveClient._create(googleAccount, accessToken);
await component._initGoogleDriveApi();

return component;
}

// Attach the needed headers to the http client.
// Initializes the DriveApi with the auth client
Future<void> _initGoogleDriveApi() async {
final gAuth.AccessCredentials credentials = gAuth.AccessCredentials(
gAuth.AccessToken(
'Bearer',
_accessToken,
DateTime.now().toUtc().add(const Duration(days: 365)),
),
null, // We don't have a refreshToken at this example
[gApi.DriveApi.driveAppdataScope],
);
var client = gAuth.authenticatedClient(http.Client(), credentials);
var localAuthHeaders = await _googleAccount.authHeaders;
var headers = localAuthHeaders;
var authClient = AuthClient(client, headers);
_driveApi = gApi.DriveApi(authClient);
}

Now we can define the private api methods:

Get file ID
Each file on google drive has its own unique ID.
To get the file ID use the following:

Future<String?> _getFileIdFromGoogleDrive(String fileName) async {
gApi.FileList found = await _driveApi.files.list(
q: "name = '$fileName'",
);
final files = found.files;
if (files == null) {
return null;
}

if (files.isNotEmpty) {
return files.first.id;
}
return null;
}

note: GoogleDrive does’t take the file name into consideration when creating a new file. therefore file multiplication can occur if not being handled carefully.

Download a file

Future<String?> _downloadFileToDevice(String fileId) async {
gApi.Media? file = (await _driveApi.files.get(fileId,
downloadOptions: gApi.DownloadOptions.fullMedia)) as gApi.Media?;
if (file != null) {
final directory = await getApplicationDocumentsDirectory();
final saveFile = io.File('${directory.path}/$fileName');
final first = await file.stream.first;
saveFile.writeAsBytes(first);
return saveFile.readAsString();
}
return null;
}

Create a file and upload it to the Drive

Future<String?> _createFileOnGoogleDrive(String fileName,
{String? mimeType,
String? content,
List<String> parents = const []}) async {
gApi.Media? media;

// Checks if the file already exists on Google Drive.
// If it does, we delete it here and create a new one.
var currentFileId = await _getFileIdFromGoogleDrive(fileName);
if (currentFileId != null) {
await _driveApi.files.delete(currentFileId);
}

if (fileName == fileName && content != null) {
final directory = await getApplicationDocumentsDirectory();
var created = io.File("${directory.path}/$fileName");
created.writeAsString(content);
var bytes = await created.readAsBytes();
media = gApi.Media(created.openRead(), bytes.lengthInBytes);
}

gApi.File file = gApi.File();
file.name = fileName;
file.mimeType = mimeType;
file.parents = parents;

// The acual file creation on Google Drive
final fileCreation = await _driveApi.files.create(file, uploadMedia: media);
if (fileCreation.id == null) {
throw PlatformException(
code: 'Error remoteStorageException',
message: 'unable to create file on Google Drive',
);
}

print("Created File ID: ${fileCreation.id} on RemoteStorage");
return fileCreation.id!;
}

And the public methods of the client are:

uploadFile(String fileContent) async {
try {
String? folderId = await _createFileOnGoogleDrive(appDataFolderName,
mimeType: folderMime);
if (folderId != null) {
await _createFileOnGoogleDrive(fileName,
content: fileContent, parents: [folderId]);
}
} catch (e) {
print("GoogleDrive, uploadfileContent $e");
}
}
Future<String?> downloadFile() async {
try {
var fileId = await _getFileIdFromGoogleDrive(fileName);

if (fileId != null) {
final fileContent = await _downloadFileToDevice(fileId);
return fileContent;
}
print("File not found on storage");
return null;
} catch (e) {
print(e);
return null;
}
}

The full GoogleDrive client code:

Security measures

It is to use encryption for sensitive user data before storing it on the cloud.
You can read more about encryption in flutter at the following post.

Conclusion

Google Drive is a great platform to insure that the authenticated user would have his own private data on the cloud.

--

--

Oz Ben

Mobile developer, Mechanical Engineer ,technology obsessed ,DJ and a Surfer . https://www.linkedin.com/in/oz-benoved