9

I am working on an app that stores user's data locally. I have used the Room database to store the user-created data in an Android device. Now if the user has more then one device then the data should be available on all their (Android) devices without any central database or web services.

Note: for the iOS side I used CloudKit to sync data between iOS devices. Is there any way to do the same for Android devices? Please note I don't want to store the data on any central database or webserver or any central cloud storage. It should be owned by the user only. (i.e. it should be synced via the user's Google account). Note: I want a native android solution and cross-platform (Android to iOS) sync is not required.

Update: Till now I found some options like Firestore and Realtime database and some AWS services also provide the sync facility but the issue with all these is that they all store the user data at a central cloud location.

So my question still remains the same - Is there a way to sync data between android devices using Google Drive or something similar where we can utilize the user's own cloud storage rather than storing user data in a central database.

Bharat
  • 2,987
  • 2
  • 32
  • 48
  • if it has to be synced via google account why don't you create a backup in google drive? – Gautam Apr 11 '20 at 12:58
  • Backup is something else where we put our database (as a file) on the drive every time we take a backup and download it when we need to restore it. I need a solution for real-time sync between devices. – Bharat Apr 11 '20 at 16:33

2 Answers2

11

Edit

There is no native/default/out of the box mechanism to sync data in real time on Android

There are surrogates though.

In context of Android two major options appear:

  • Sockets with some persistent storage - you may implement backend server with support of sockets and add this support for your android(ios/web/desktop) app. This is the most common and pretty low level mechanism to implement real time sync between devices. Pros : Universal across platforms, industry standard, a lot of info online. Cons: as it is relatively low level thing a lot of code is required, may be quite complex and complicated if there are a lot of sync evens, needs backend, needs separate persistence. Major implementations: Socket.io

  • Firebase Realtime Database/ Cloud Firestore - basically wrapper around sockets in conjunction with some database. "Pros": Doesn't require neither backend nor database, free for prototype/low-load apps, pretty straight forward implementation, cross platform, works pretty well. Cons : A lot of boilerplate if you want to go "clean way", you have no control over the inner processes, not very obvious payment calculation on high loads, you link yourself with a third party backend with specific API - well defined architecture required if you will want to move from it somewhere else without complete app remake. comparison: firebase cloud firestore vs realtime database

I will not get into details with the implementation because the official documentation is quite straightforward in this case(unlike with google drive)

Cloud Firestore documentation

Realtime Database documentation

Edit end

Yes. There is a possibility to privately store data using Google Drive. It will be accessible only by your application. Moreover it won't occupy any space in users Google Drive which is pretty handy. Cross platform in this case possible but it needs to be implemented separately for iOs and Android(not supported out of the box by SDK).

So the solution is based on Google Drive API and more specifically on Store application-specific data part of it.

The only visible for the user action regarding this will be google login prompt.

Here is a detailed quickstart for Java

So in order to do it you will need:

  • Add dependencies;

  • Implement google auth like described here and here

  • Very important Authenticate users with drive.appdata and drive.file scopes. Also enable Google Drive API in your Google Cloud Console. All this is described here

  • Figure out where your Room SQLite file is situated like this: String currentDBPath = context.getDatabasePath("your_database_name.db").getAbsolutePath();

  • Upload file to the Google Drive App Folder like it is described here

Basically it will look like this

  • Enable Google Dive API in Console

  • Add dependencies(maybe need to be updated to relevant versions)

implementation 'com.google.android.gms:play-services-auth:17.0.0'// for google sign in

// for drive integration
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.http-client:google-http-client-gson:1.26.0'
implementation('com.google.api-client:google-api-client-android:1.26.0') {
exclude group: 'org.apache.httpcomponents'
}
implementation('com.google.apis:google-api-services-drive:v3-rev136-1.25.0') 
{
exclude group: 'org.apache.httpcomponents'
} 
  • In android gradle tag
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
  • Manifest
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • Google auth with required scopes
       val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(
                 Scope(Scopes.DRIVE_FILE),
                 Scope(Scopes.DRIVE_APPFOLDER)
            )
            .requestEmail()
            .build()
        googleSingInClient = GoogleSignIn.getClient(this, gso)
        val signInIntent = googleSingInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
        ... // handle sign in result
  • Set up google drive client service(where context is)
        val mAccount = GoogleSignIn.getLastSignedInAccount(this)

        val credential =
            GoogleAccountCredential.usingOAuth2(
                applicationContext, Collections.singleton(Scopes.DRIVE_FILE)
            )
        credential.setSelectedAccount(mAccount.getAccount())
        val googleDriveService =
            Drive.Builder(
                AndroidHttp.newCompatibleTransport(),
                GsonFactory(),
                credential
            )
                .setApplicationName("Your app name")
                .build()
  • Create and send file
        val fileMetadata = File()
        fileMetadata.setName("your_database_name.db")
        fileMetadata.setParents(Collections.singletonList("appDataFolder"))
        val databasePath = context.getDatabasePath("your_database_name.db").absolutePath
        val filePath = File(databasePath)
        val mediaContent = FileContent("application/sqlite", filePath)
        val file = driveService.files().create(fileMetadata, mediaContent)
            .setFields("id")
            .execute()
  • Search file on other device
        val files = driveService.files().list()
            .setSpaces("appDataFolder")
            .setFields("nextPageToken, files(id, name)")
            .setPageSize(10)
            .execute()
        for (file in files.getFiles()) {
            // get file here 
        }

A bit more info you may get here

Hope it helps.

Pavlo Ostasha
  • 14,527
  • 11
  • 35
  • 1
    Thanks, Paul for this detailed answer. Looks like it is a backup and restore model where we trigger file upload and similarly we download the same file. We need to do these tasks manually(by triggering some events in our app). But, I need a realtime data sync between devices i.e. when an update (any CRUD operation) happens in one device it should be available on other devices in real-time. – Bharat Apr 15 '20 at 03:37
  • Anyway, I'll give it a try. – Bharat Apr 15 '20 at 04:23
  • 1
    That means only firebase if you want to go with serverless. Cloud Firestore or Real-time Database. And what you want is not possible without centralized storage on some backend or cloud. I will add more info in edit of the answer – Pavlo Ostasha Apr 15 '20 at 07:37
  • 1
    Thanks! My observation is the same. I was under the impression that if iOS does provide the sync mechanism through the iCloud account then there should some equivalent on android too. – Bharat Apr 15 '20 at 11:25
  • Adde link to documentation and some explanations to my answer regarding realtime sync – Pavlo Ostasha Apr 15 '20 at 14:14
  • Thanks, mate for your efforts to help me with this. Actually, I already tried the FireStore and Realtime Database from Firebase. The problem with all these solutions is that they all take user data out of the user's control by storing it on some central cloud storage and this is what I (my client) do not want. The data should be private to the user. In iOS, CoreData + iCloud sync do the same. – Bharat Apr 15 '20 at 17:08
  • Well that is easy - just encrypt the data you store. – Pavlo Ostasha Apr 15 '20 at 19:24
  • Data is already encrypted. The first thing my client wants that data should not go out of the user's control even if it is encrypted :( – Bharat Apr 16 '20 at 03:54
0

You can use Cloud FireStore from Firebase for this https://firebase.google.com/docs/firestore

This is scalabe and Google Drive API has been deprecated. i personally have used FireStore for one of the app and it works pretty good. Bharat

pramod_m
  • 184
  • 1
  • 9
  • 2
    Thanks, Pramod for your efforts to help me with this. Actually, I already tried the FireStore and Realtime Database from Firebase. The problem with all these solutions is that they all take user data out of the user's control by storing it on some central cloud storage and this is what I (my client) do not want. The data should be private to the user. In iOS, CoreData + iCloud sync does the same. – Bharat Apr 21 '20 at 03:52
  • That's obviously there we can't help that. If you don't want to do that you might have to implement a custom SDK to do this for the app . any SDK in that case does that. – pramod_m Apr 21 '20 at 06:27