How To Create An Android App That Posts Pictures To Google Sheets

FYI

I've created an app that uploads camera/gallery images into Google Sheets.

All the source is available. I found lots of people trying to do this, and none of their examples worked for me.

I would have published on the site, but it wouldn't let me publish a project that uses extensions.

I hope this is useful to someone.

1 Like

You missed this one then :wink:

https://groups.google.com/d/msg/mitappinventortest/liWppNXqHXM/5Ht506yRBwAJ

Also you don't show your web app script, always useful to see how others do it....

Nope :upside_down_face:

  • I couldn't get that to work. I think I might see now where i went wrong, dur!
  • The sheet is available, the script is inside that. File > Make a copy
  • That script doesn't return anything
  • That script doesn't thumbnail the images

It does do a better job of handling MIME type though.

Errors out on make a copy....

image

Er, sorry... try this...

Nope, same error....tested two other sheets shared with me, both make a copy OK.

Er... doesn't make sense... sorry...

Code.gs
--
/*
Uses Library: 1T03nYHRho6XMWYcaumClcWr6ble65mAT8OLJqRFJ5lukPVogAN2NDl-y
From: https://github.com/tanaikech/ImgApp 
to resize images
*/

var img_leader = "data:image/jpeg;base64," //I'm assuming a JPEG here, tsk, tsk...
var imageFolderId = "148kImTGptbKdckcrV3sL3xUCMqXRRdWu" //WHERE WE WANT THE IMAGES TO BE SAVED//CHANGE ME
var folder = DriveApp.getFolderById(imageFolderId)

//Used to display the last three images
function doGet(e) {
  var action  =  e.parameter.action 
  var scriptUrl = ScriptApp.getService().getUrl()
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
  var values = sheet.getDataRange().getValues()
  var headers = values.shift()
  
  if (action == "list" | action == null){  
    
    values = values.slice(Math.max(values.length - 3, 1))//Get the last three items
    values.reverse()
    
    try{
      var lastRow = sheet.getLastRow()
      var data = sheet.getRange(lastRow, 10).getValue()
      Logger.log(data.length + " chars")
      var template = HtmlService.createTemplateFromFile('index.html')
      template.scriptUrl = scriptUrl
      template.values = values
      var homepage = template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) 
      return homepage;
    }catch(e){
      return ContentService.createTextOutput(e)
    }
    
    
  }else if (action == "view"){//View just one image
    
    var timestamp = Number(e.parameter.timestamp)
    for (v in values){//looping slow but...
      var row = values[v]
      try{
        if (row[0].getTime() == timestamp){
          var template = HtmlService.createTemplateFromFile('view.html')
          template.data = row[3] // the full size image data
          var homepage = template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)  
          return homepage;
        }
      }catch(e){
        return ContentService.createTextOutput(e)
      }
      
      
    }
  }
  
}

//Called by App Inventor App to upload a picture and data
function doPost(e) {
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
  var now = new Date()
  try{
    var image = e.parameter.image
    var imageFileUrl = ''
    
    try{ // to make an image
      var name = "App_Inventor_" + now.getTime() + ".jpg"
      var decoded = Utilities.base64Decode( image.toString() )//Utilities.Charset.UTF_8
      var blob = Utilities.newBlob(decoded, MimeType.JPEG, name);
      var imageFile = folder.createFile(blob)
      var thumbnailData = getThumbnail( imageFile.getId()) 
      imageFileUrl = imageFile.getUrl()
    }catch(e){
      log(e + " " + e.stack)
    }
    
    var asimuth = e.parameter.asimuth
    var pitch = e.parameter.pitch
    var roll = e.parameter.roll
    var angle = e.parameter.angle
    var text = e.parameter.text
    var latlng =  e.parameter.latlng
    var address = e.parameter.address
    
    //timestamp	text	latlng	image	image URL	asimuth	pitch	roll	angle																		
    var data = [now, text, latlng, image, imageFileUrl, asimuth, pitch, roll, angle, thumbnailData, address]
    sheet.appendRow(data)
    SpreadsheetApp.flush()//Do I need this?
    
    
    return ContentService.createTextOutput("IMAGE UPLOADED OK");
  }catch(e){
    return ContentService.createTextOutput(e + " " + e.stack )
  }
}

var thumbnailWidth = 32
function getThumbnail(id){
  //https://github.com/tanaikech/ImgApp
  //"16y9kDWjCmSjx_YbF3ZSN_UvRL74I_5dE"
  
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
  
  var imageFile = DriveApp.getFileById(id)
  var tempImageFile = imageFile.makeCopy(folder)
  
  var thumbnailBlob = tempImageFile.getBlob() //
  log( ImgApp.getSize(thumbnailBlob)  )
  
  var res = ImgApp.doResize(tempImageFile.getId(), 48)  
  
  var width = res.resizedwidth;
  var height = res.resizedheight;
  var newName = tempImageFile.getName().replace(".jpg", "_thumb.jpg")
  var newNewFile = DriveApp.createFile(res.blob.setName( newName ));
  folder.addFile(newNewFile)
  
  log( ImgApp.getSize( newNewFile.getBlob() ))
  
  
  var thumbnailData = Utilities.base64Encode( newNewFile.getBlob().getBytes())
  var thumbnailDataLength = thumbnailData.length
  //sheet.getRange(2, 10).setValue(thumbnailData)
  tempImageFile.setTrashed( true )
  DriveApp.removeFile(tempImageFile) //remove the copy that got resized.
  return thumbnailData
}

Utils.gs
--
var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Log")


function log(s){
  logSheet.appendRow([s])
  
}

function prettyprint(object){
  var text = JSON.stringify(object, null, 4); //you can specify a number instead of '\t' and that many spaces will be used for indentation...
  return text
}

function setRowHeights(){
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
  var lastRow = sheet.getLastRow()
  sheet.setRowHeights(1, lastRow, 20)
  //Logger.log( "There are " + lastRow + " rows, including the header")
}

function howManyRows(){
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
  var lastRow = sheet.getLastRow()
  Logger.log( "There are " + lastRow + " rows, including the header")
}

function test(){ //So some Extensions encode the path, and not the image
  var s = "/storage/emulated/0/DCIM/Camera/pic.jpg"
  Logger.log( Utilities.base64Encode(s))
}

function testCreateImage(){
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName("Images")
   var lastRow = sheet.getLastRow()
   var image = sheet.getRange(lastRow, 4).getValue()
   
  try{ // to make an image
      var decoded = Utilities.base64Decode( image ) //Utilities.Charset.UTF_8
      var blob = Utilities.newBlob(decoded, MimeType.JPEG, "App Inventor.jpg");
      //var imageFile = DriveApp.createFile(blob)
      var imageFile = folder.createFile(blob)
      imageFileUrl = imageFile.getUrl()
      log(imageFileUrl)
    }catch(e){
      log(e + " " + e.stack)
    }
    
  
}

function include_drive_file(id){
  var file = DriveApp.getFileById(id)
  var source = file.getAs('text/plain').getDataAsString()
  return source
}


/* CACHE-ING CALLS - USEFUL FOR SPEEDING UP ACCESS ON DATA THAT DOESN'T CHANGE MUCH */
function include_drive_file_cache(id){
  try{
  var cache = CacheService.getPublicCache();
  var items_data = cache.get(id);
  if (items_data == null) {
    items_data = include_drive_file(id);
    cache.put(id, Utilities.jsonStringify(items_data), 3600);
  } else {
    items_data = Utilities.jsonParse(items_data);
  }
  return items_data;
  }catch(e){
    Logger.log( e ) //Sometimes if the data is too big, it can't be cached it seems, so it just bails and reads it.
    var data = include_drive_file(id)
    Logger.log( data.length)
    return data  
  }
}

index.html
--
<!DOCTYPE html>
<html>
  <head>
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>
    <base target="_top">
<style>
#warning{ 
    visibility:hidden;
  }
#container{
 text-align: center;
 display: block;
}

img{
 margin:10px;
}
</style>

<script>
var timer

function hider(){
   //var ele = $("#warning"); 
   const el = $("#warning")
  if (el) {
   el.css('display','none')
  } else {
  //console.log("argh!")
  }
  
   clearInterval(timer);   
}

$(function() {
    timer = setInterval(function(){ hider(); }, 3000);    
});

</script>

  </head>
  <body>
  <div id="container">
  <p>Displays the last 3 images uploaded.</p>
    <?
    for (v in values){
        var row = values[v] 
        var data = row[9]
        var timestamp = row[0].getTime()
    ?>
        <a href="<?=scriptUrl?>?action=view&timestamp=<?=timestamp?>"><img src="data:image/jpeg;base64,<?!=data?>" width="96"></a>
     <?}?>
    </div>
  </body>
</html>


view.html
--
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
    #warning{ 
    visibility:hidden;
}
#container{
 text-align: center;
 display: block;
}

img{
 margin:10px;
}

</style>
  </head>
  <body>
  <div id="container">
  
        <img src="data:image/jpeg;base64,<?!=data?>" width="100%">
    
    </div>
  </body>
</html>

Hang on ...even I can't copy it... Google are having issues.

Wow - you really went for it on the script coding :smiley:

My script originally came from Tanaike as well.