androidupload

How to automatically upload data to a shared sheet when Internet becomes available?


Context I am making an app that takes basic user inputs and puts them in a spreadsheet (using Apache POI and excel right now, but google sheets would work, too).

Right now, the program writes the data to an excel file in the documents section of my phone, and to access it on my computer I have to email it to myself. I can't input the data directly to an online google sheet, because I use the app in areas where I don't have internet connection.

Actual question My app currently stores data locally. Can I code the app to automatically upload this data to a shared sheet when internet becomes available?

Current code My current code simply creates a local XSSF Workbook (or accesses an existing one if it exists) and inputs the data into it. This is a bit of a simplified version (the real version creates separate sheets to group different types of data, adds titles and headers, etc.) but the basic mechanics are the same.


int rowCount = sheet.getLastRowNum();

Workbook workbook;
Sheet sheet;

// make workbook or get existing one
if (filePath.exists()) {
    FileInputStream fis = new FileInputStream(filePath);
    workbook = new XSSFWorkbook(fis);
    fis.close();
} else {
    workbook = new XSSFWorkbook();
}

//enter data into spreadsheet from rowList
sheet = workbook.getSheet(0)
for (int i = 0; i < rowList.size(); i += 3) {
    Row row = sheet.createRow(++rowCount);
    for (int j = 0; j < 3; j++) {
        Cell cell = row.createCell(j);
        cell.setCellValue(rowList.get(i + j));
    }
}

FileOutputStream fos = new FileOutputStream(filePath);
workbook.write(fos);
fos.close();
workbook.close();

Solution

  • Doing something when network becomes available

    Initial ideas that you shouldn't do / not necessary

    Firstly, you could try to upload the data immediately (right there along or instead of your spreadsheet code, in the foreground), if Internet is available. I.e.

    Otherwise, you could instruct Android, to upload the data whenever Internet becomes available. You could implement that by waiting for Internet with a Broadcast receiver as presented by the accepted answer in this SO post. That's unreliable though (close the app and the upload job is gone) and you need a lot of code just for getting to know when Internet is available.

    The recommended approach is WorkManager, which allows you to define background work

    Doing such work in the background rather than the foreground is absolutely best practice, because you don't slow down the main thread and potentially cause the application's UI to lag.

    AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET" />
    

    build.gradle.kts (app):

    dependencies {
        val work_version = "2.10.0"
        // Kotlin + coroutines
        implementation("androidx.work:work-runtime-ktx:$work_version")
    }
    

    Note: Android Studio suggests to migrate that to the new Version Catalog format (Alt+Enter). That is cleaner. I just show the full definition in one file here, for shortness.

    UploadWorker.kt:

    class UploadWorker(
        context: Context,
        workerParams: WorkerParameters
    ): CoroutineWorker(context, workerParams) {
    
       override suspend fun doWork(): Result {
          try {
             // upload data to shared sheet here
             return Result.success()
          } catch (e: Exception) {
             return Result.retry()
          }
       }
    
    }
    

    MainActivity.kt or somewhere else (define work request):

    val context = LocalContext.current
    
    val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.CONNECTED)
       .build()
    
    val work: WorkRequest =
       OneTimeWorkRequestBuilder<UploadWorker>()
           .setConstraints(constraints)
           .build()
    
    WorkManager.getInstance(context).enqueue(work)
    

    If you want to dive deeper into WorkManager, I recommend Philipp Lackner's YouTube tutorial and this History of WorkManager article.

    Uploading to an online spreadsheet

    The easiest way to implement the data upload would be

    Due to the lack of a specified provider, I can't provide you the exact code, but generally:

    build.gradle.kts (app):

    dependencies {
        val retrofit_version = "2.9.0"
        val gson_version = "2.9.0"
        // Retrofit
        implementation('com.squareup.retrofit2:retrofit:$retrofit_version')
        // Gson converter for JSON
        implementation('com.squareup.retrofit2:converter-gson:$gson_version')
    }
    

    ApiService.kt:

    Depends on the API you talk to, but for example:

    interface ApiService {
        @Headers("x-api-key: ...")
        @PUT("spreadsheets/1/...")
        suspend fun upload(@Body request: UploadRequest): retrofit2.Response<Unit>
    }
    

    UploadRequest.kt:

    data class UploadRequest(
        val myInput: String,
        val myInput2: Double
    )
    

    in UploadWorker.kt where I've put the comment to insert code:

    // Build Retrofit instance
    val retrofit = Retrofit.Builder()
        .baseUrl("https://.../v4/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    
    val api = retrofit.create(ApiService::class.java)
    
    val request = UploadRequest(
        myInput1 = ...,
        myInput2 = ...
    )
    
    val response = api.upload(request)
    if (response.isSuccessful) {
        return Result.success()
    } else {
        return Result.retry()
    }
    

    With this you're putting your data in the UploadRequest and the GsonConverterFactory will convert it to a JSON object before sending the API request.

    Code examples for reference

    Example projects I can offer you for reference:

    Rather use the code snippets above than copy and pasting my projects' code. They're better used e.g. for checking if you got the imports right or how to extract code to functions or composables.