You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓Kotlin项目中向Google Sheet写入数据的问题求助

Android端Kotlin接入Google Sheets API 解决未解析引用与通信问题

问题背景

我正在开发一款Android App,需要将手机端获取的GPS数据更新至Google电子表格。已完成云项目创建、开启Sheets API、获取API密钥,并添加了以下依赖:

implementation 'com.google.api-client:google-api-client:2.2.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
implementation 'com.google.apis:google-api-services-sheets:v4-rev20230227-2.0.0'
implementation 'com.google.http-client:google-http-client-gson:1.42.3'

但现有教程多为Java/Python版本,转Kotlin后出现FATAL EXCEPTION,同时存在两个核心未解析引用错误:

  • GoogleAccountCredential 未解析引用
  • sheetsService 未解析引用
    另外对credentials.json的放置位置和授权方式也存在疑问。

问题代码示例

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat

import com.google.api.client.googleapis.auth.oauth2.*
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.http.HttpTransport
import com.google.api.services.sheets.v4.*
import java.io.*
import java.util.*

//private val CREDENTIALS_FILE_PATH = "/credentials.json"
//I see Java code using .json file to auth the permission,I put file in "app\src\main\res", but I'm not sure is crroect or not.

class MainActivity : AppCompatActivity() {
 
    private val SCOPES = listOf(SheetsScopes.SPREADSHEETS)
    private val googleSheetID = "MYSHEETID"
    private val googleSheetName = "MYSHEETNAME_TOO"
    private val range = "B1"
    //I have changed that to my sheet ID.
    val credential = GoogleAccountCredential.usingOAuth2(this, listOf(SheetsScopes.SPREADSHEETS))
    //I create OAuth2 and service account too. 
    //GoogleAccountCredential UNRESOLVE REFERENCE error
    
    @SuppressLint("RestrictedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)        

        val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        val locationListener = object : LocationListener {
            override fun onLocationChanged(location: Location) {
                findViewById<TextView>(R.id.longitude).text = location.longitude.toString()
                findViewById<TextView>(R.id.latitude).text = location.latitude.toString()
            }
            override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
            override fun onProviderEnabled(provider: String) {}
            override fun onProviderDisabled(provider: String) {}
        }
        
        findViewById<Button>(R.id.load_from_GPS).setOnClickListener {
            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this,"@string/btn_GPS",Toast.LENGTH_SHORT).show()
                ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 1)
                return@setOnClickListener
            }
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, locationListener)
        }

        findViewById<Button>(R.id.reportBt).setOnClickListener {
            val textView0: TextView = findViewById(R.id.longitude)
            val textView1: TextView = findViewById(R.id.latitude)
            val textTemp0 = textView0.text.toString()
            val textTemp1 = textView1.text.toString()
            textTemp = textTemp0+","+textTemp1
            val valueRange = ValueRange().setValues(listOf(listOf(textTemp)))
            
            sheetsService.spreadsheets().values()
                .update(googleSheetID, googleSheetName + "!" + range, valueRange)
                .setValueInputOption("RAW")
                .execute()
        }//sheetsService UNRESOLVE REFERENCE error  
    }   
}

解决方案

1. 修复依赖缺失

GoogleAccountCredential属于Android端OAuth库,当前依赖中的google-oauth-client-jetty是桌面端组件,需替换为Android版依赖:

// 移除桌面端依赖
// implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
// 添加Android端OAuth依赖
implementation 'com.google.oauth-client:google-oauth-client-android:1.34.1'
// 添加Google账号登录支持
implementation 'com.google.android.gms:play-services-auth:20.7.0'
// 添加协程依赖(用于异步网络请求)
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'

2. 正确处理Credentials与授权

  • credentials.json需放置在app/src/main/assets目录(无assets文件夹则手动创建),而非res目录。
  • 正确初始化GoogleAccountCredential并引导用户选择Google账号:
private lateinit var credential: GoogleAccountCredential
private val REQUEST_ACCOUNT_PICKER = 1000

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    // 初始化Credential
    credential = GoogleAccountCredential.usingOAuth2(
        this,
        SCOPES
    ).setBackOff(ExponentialBackOff())
    
    // 启动账号选择界面
    startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when(requestCode) {
        REQUEST_ACCOUNT_PICKER -> {
            if (resultCode == RESULT_OK && data != null) {
                val accountName = data.getStringExtra(android.accounts.AccountManager.KEY_ACCOUNT_NAME)
                accountName?.let {
                    credential.selectedAccountName = it
                    // 账号选择完成后初始化Sheets服务
                    initSheetsService()
                }
            }
        }
    }
}

3. 初始化sheetsService

sheetsService未解析是因为未定义和初始化,需在授权完成后创建实例:

private lateinit var sheetsService: Sheets

private fun initSheetsService() {
    val httpTransport = com.google.api.client.extensions.android.http.AndroidHttp.newCompatibleTransport()
    val jsonFactory = com.google.api.client.json.gson.GsonFactory.getDefaultInstance()
    
    sheetsService = Sheets.Builder(httpTransport, jsonFactory, credential)
        .setApplicationName("你的App名称")
        .build()
}

4. 修复代码中的其他问题

  • 补充textTemp变量定义:
val textTemp = textTemp0 + "," + textTemp1
  • 网络请求不能在主线程执行,改用协程异步处理:
findViewById<Button>(R.id.reportBt).setOnClickListener {
    val textView0: TextView = findViewById(R.id.longitude)
    val textView1: TextView = findViewById(R.id.latitude)
    val textTemp0 = textView0.text.toString()
    val textTemp1 = textView1.text.toString()
    val textTemp = textTemp0 + "," + textTemp1
    val valueRange = ValueRange().setValues(listOf(listOf(textTemp)))

    lifecycleScope.launch(Dispatchers.IO) {
        try {
            sheetsService.spreadsheets().values()
                .update(googleSheetID, "$googleSheetName!$range", valueRange)
                .setValueInputOption("RAW")
                .execute()
            
            launch(Dispatchers.Main) {
                Toast.makeText(this@MainActivity, "数据提交成功", Toast.LENGTH_SHORT).show()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            launch(Dispatchers.Main) {
                Toast.makeText(this@MainActivity, "提交失败:${e.message}", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

5. 添加网络权限

在AndroidManifest.xml中添加:

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

内容的提问来源于stack exchange,提问作者FrankieK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:27:14