iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
1
Mobile Development

程式初學:Android與Kotlin系列 第 29

Day 29--savedInstanceState狀態保存,讀取Google Sheet

  • 分享至 

  • xImage
  •  

由下圖可知
onSaveInstanceState()是當生命週期進入onStop()時會被呼叫的callback

所以每當app進入後台時,便會執行onSaveInstanceState(),可視爲一個安全措施
onSaveInstanceState()可打包少量的資訊,於當app恢復時使用

在MainActivity覆寫

override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}

執行app並收進後台
logcat顯示onSaveInstanceState()確實在onStop()後呼叫

fun onSaveInstanceState(outState: Bundle)
參數outState是Bundle型態,此型態爲key-value的集合
key固定爲String,value可存放基本型態

因爲系統將bundle存放在RAM內,且有容量限制
通常存放的容量應遠小於100k,否則易導致crash

在MainActivity外層,宣告幾個固定常數字串作爲key

const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

class MainActivity : AppCompatActivity() {...}

onSaveInstanceState()內將revenue(價格,整數型態)加入bundle

outState.putInt(KEY_REVENUE,revenue)    //putInt(key,value)

//以及數量,timer計數等都加入
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

以上就完成將資料打包的動作
接著看onCreate()

override fun onCreate(savedInstanceState: Bundle?) {...}

onCreate()的預設用法一直都是有bundle參數
所以前面打包好後,就可以到onCreate()內取出來

if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}

常用模式爲檢查bundle不爲null再往下執行
if (savedInstanceState != null)
然後加入bundle是putInt,取出就是getInt

執行app,異動內容後,將app移到後台並使用terminal關閉
檢查app的內容狀態有保留(實際有時還是會歸零,不知道是否因存在記憶體的bundle也被清掉)

但可以看到進後台時,是甜甜圈的狀態,回前台又變成杯子蛋糕
原因是甜點圖片由這個函數showCurrentDessert()依照dessertsSold的數值,控制現在該顯示哪張圖片

但這個函數是點擊圖片才觸發,因此當app回到前台尚未點擊時
就須在if (savedInstanceState != null)檢查中加入
若bundle有資料,表示showCurrentDessert()也該執行

if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}

額外參考: If the activity is being re-created, the onRestoreInstanceState() callback is called after onStart(), also with the bundle. Most of the time, you restore the activity state in onCreate(). But because onRestoreInstanceState() is called after onStart(), if you ever need to restore some state after onCreate() is called, you can use onRestoreInstanceState()

配置改變

有一些情況屬於裝置重大的配置改變時,activity / fragment都會被destroy然後重新onCreate
例如改變手機的語系設定,外接螢幕或鍵盤,旋轉手機等等
因爲這些配置改變時,app的內容可能有需要跟著調整的地方
系統爲了符合app的顯示,最便利的方式就是重新onCreate()

所以當預期app可能會需要在配置改變時,仍然保持相同狀態
就可利用onSaveInstanceState(),將所需的資料存入bundle
以便在重新onCreate()時,恢復先前狀態

p.s
在使用模擬機測試時,發現有時調回app,有存入bundle,但還是顯示歸零
這部分還不太清楚什麼問題,請教導師說可先將手機所有其它在後台的程式都清除再試
就沒有再遇到

讀取Google Sheet

Sheet ID

開一個Google sheet,工作表1更名爲Sheet1

權限設定公開檢視

連結網址/ /的中間那串就是這個資料表的SheetID
https://docs.google.com/spreadsheets/d/1uoTGeYfi8SdhxPgS3nFWDb-3-KJMibDpErfIm4c8ZH0/edit#gid=0
例如:
1uoTGeYfi8SdhxPgS3nFWDb-3-KJMibDpErfIm4c8ZH0

API Key

https://console.developers.google.com/flows/enableapi?apiid=sheets.googleapis.com
建立Google Api 參考

如果出現OAuth的東西先不用理它
選建立憑證

選API金鑰

完成後就會看到出現API金鑰

可以設定限制

JSON

有了憑證權限就可以將如下網址
與資料範圍{Range},{Sheet ID},{API Key}合併
一起以瀏覽器開啓,就會看到JSON格式
https://sheets.googleapis.com/v4/spreadsheets/{Sheet ID}/values/{Range}?key={API Key}

也可以丟到 https://jsoneditoronline.org/ 解析

build.gradle

開一個android studio專案,build.gradle加入dependencies

//Import okHttp dependencies
    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    implementation 'com.squareup.okhttp3:okhttp:3.4.1'

//Import Retrofit dependencies
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.retrofit2:retrofit:2.1.0'

AndroidManifest.xml

網路存取權限也別忘了,要加
<uses-permission android:name="android.permission.INTERNET" />

Kotlin data class from JSON

有了JSON就再使用這個Kotlin data class from JSON套件協助轉成data class吧

建立一個BookData.kt檔,Generate

data class BookData(
    @SerializedName("majorDimension")
    val majorDimension: String = "", // ROWS
    @SerializedName("range")
    val range: String = "", // Sheet1!A2:B4
    @SerializedName("values")
    val values: List<List<String>> = listOf()
)

API interface

然後建立api

internal object APIClient {

    lateinit var retrofit: Retrofit

    val client: Retrofit
        get() {

            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            val client = OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .connectTimeout(2, TimeUnit.MINUTES)
                .readTimeout(2, TimeUnit.MINUTES)
                .build()


            retrofit = Retrofit.Builder()
                .baseUrl("https://sheets.googleapis.com/v4/spreadsheets/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build()

            return retrofit
        }
    val apiInterface: ApiInterface = APIClient.client.create(ApiInterface::class.java)
}

interface ApiInterface {
    @GET("{SheetId}/values/{Range}?key={API Key}")
    fun getBooks() : Call<BookData>
}

到MainActivity呼叫

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val call = apiInterface.getBooks()
        call.enqueue(object : Callback<BookData> {
            override fun onResponse(call: Call<BookData>, response: Response<BookData>) {
                Log.d("Success!", response.toString())

                if (response.isSuccessful) {
                    response.body()?.values?.forEach { bookList ->
                        Log.d("api", "${bookList[0]} ${bookList[1]}")
                    }
                }
            }


            override fun onFailure(call: Call<BookData>, t: Throwable) {
                Log.e("Failed Query :(", t.toString())

            }
        })
    }
}

取得Google sheet的資料了


上一篇
Day 28--Complex lifecycle situations
下一篇
Day 30--Retrofit 登入練習
系列文
程式初學:Android與Kotlin30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言