Day 29--savedInstanceState状态保存,读取Google Sheet

由下图可知
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[-1] 今天我想来点Kibana的TSVB

>>:  Day 29 - 大杂烩好吃

Annotation 的设计与想法

Annotation 要怎麽定义会影响使用这个 library 的使用者体验,annotation ...

test

test 这是H4标题 ...

javascript HTML DOM4

最後我们学习如何控制多个表单的开合 ...

第15章:管理与设定网路介绍(二)

前言 本章节,是要讲述如何查看网路设定与设定在主机上的网路资讯。 识别与取得网路介面资讯 在一台主机...

DAY28 - EDM切版

还有一种类型的切版,是EDM切版, EDM切版是什麽呢? 指的就是信里面看到的版面,像是下面这个就是...