今天來寫點關於ViewModel儲存狀態模組

ViewModel雖然現在網路上有很多人寫關於這部分的東西,也有很多實例幫助我們了解ViewModel

不過我有段時間其實一直不明白他跟螢幕旋轉中的儲存狀態(onSaveInstanceState)跟這個ViewModel到底差在哪裡

直到...呃,好吧我工作也沒有碰到要用ViewModel,就是有天吃飽太閒再看文檔的時候才好像稍微有了解了那麼億點點?

 

總之,我個人是看到了一個重點。也就是他的生命週期的部分

 

說明 ViewModel 在活動變更狀態時的生命週期。

 

有木有看到旁邊那一大條綠綠的ViewModelScope? 沒錯,這個意思很明顯就是

ViewModel凌駕於生命週期之上!!(喂別亂下定論)( ͡ಠ ʖ̯ ͡ಠ)

好啦其實這個定論是正確的.....

 

恩,其實在Android官方文檔就有提到,ViewModel就是最為一個「可儲存任何東西」的存在

並且這個存在是凌駕於生命週期之上

因此在官方範例中,他舉了很經典的「螢幕旋轉」做為範例來解釋

而我呢?當然也沒那麼聰明地也拿螢幕旋轉來解釋啦XDDD

阿,說到螢幕旋轉也可以看看我對於螢幕旋轉的文章喔!

->碼農日常-『Android studio』關於Android手機螢幕旋轉的一些大小事

 

好,事不宜遲,我們開始今天的範例

還有Github

-> https://github.com/thumbb13555/AndroidBlogExamples/tree/main/ViewModelExample

 

開始吧!


 

1. 不好意思再廢話個幾句

 

恩...這段一樣式廢話啦

不過看了GIF之後大概也知道了今天的主題是做一個碼表程式

那其實我當初在考量要如何寫ViewModel的時候,我懂了是懂了,不過我真正想做的事情是

寫出一篇能夠清楚點出使用ViewModel與使用onSaveInstanceState兩者之間的差別

 

畢竟兩者都是螢幕旋轉,如果都是螢幕旋轉的話,那用ViewModel無非是脫褲子放屁或者是殺雞焉用牛刀看你尬藝哪個就用哪個(笑)

最早我認識ViewModel時是看這個範例

->https://qiita.com/KIRIN3qiita/items/7d833e2c010c0b2c02d9

(抱歉了,我住日本所以Google都是優先丟給我日文的文章)

 

這位大大的文章用了一種極為簡單的方式使我很快上手了解了ViewModel,我非常感謝

不過他的範例有一個尷尬的地方,那就是他的範例用ViewModel與使用onSaveInstanceState兩者去寫都看不出差異

也因此後來我想了良久,想到了在使用像碼表紀錄實時變數的東西,是經不起需要「螢幕旋轉」中間的延遲

 

什麼意思?意思是說其實Android手機在螢幕旋轉時,都會有那麼幾毫秒的Lag

而這個Lag雖然看起來無傷大雅,但是如果紀錄像是以豪秒為單位的變數時,這個豪秒恐怕會影響到紀錄的準確性

因此為了克服這個問題,我想到如果把執行緒都寫在ViewModel內的話,便不會有這種問題了吧!

所以當初在選擇題材的時候,其實也是下了點功夫去思考的

 

OK大致就是這樣,總之開始吧!

 


 

2. 介面&撰寫ViewModel

 

首先介面,沒什麼好提的,直接丟

activity_main.xml

截圖 2023-01-29 下午7.30.18

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_stopwatch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0:00:00"
        android:textColor="@color/black"
        android:textSize="32dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6" />

    <Button
        android:id="@+id/button_stopwatch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

好,接著來到重點。就是寫ViewModel的部分

首先我的專案文件長這樣,請直接new 一個Java Class

截圖 2023-01-29 下午7.33.31

然後讓他繼承ViewModel

截圖 2023-01-29 下午7.34.39

 

最後,我在裡面寫一些需要用到的組件,直接全給吧!

StopwatchModel.java

public class StopwatchModel extends ViewModel {
    private int nanoSeconds = 0;
    private boolean isRunning = false;
    private String time = "0:00:00";
    private CallBack callBack;

    public void setCallBack(CallBack callBack) {
        this.callBack = callBack;
    }
    private Handler handler = new Handler();
    private Runnable r = new Runnable() {
        @Override
        public void run() {
            int min = nanoSeconds / 3600;
            int sec = (nanoSeconds % 3600) / 60;
            int nano = nanoSeconds % 60;

            time = String.format(Locale.getDefault(),
                    "%d:%02d:%02d", min,
                    sec, nano);
            callBack.callbackListener(time);
            nanoSeconds++;

            handler.postDelayed(this, 10);
        }
    };
    public void stopwatchSwitch(){
        if (!isRunning) {
            handler.post(r);
            isRunning = true;
        } else {
            handler.removeCallbacks(r);
            isRunning = false;
            nanoSeconds = 0;
        }
    }
    interface CallBack{
        void callbackListener(String time);
    }
}

首先是紅底白字部分,handler+runnable的一系列非同步執行這種東西對於第一次接觸的人有點小複雜,不過基本上快要是一個Set了XD

而我這裡也剛好也有寫過這部分的介紹,想延伸了解的話可以看看

->碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法

再來是黃底白字部分其實也很簡單,就是一個Callback。使這邊跑的資訊可以傳給MainActivity那邊使用

大意就是如此,其他就是一些開跑與停止的方法而已,不再贅述

 


 

3. 使用模組

 

最後就是使用我們寫的這塊模組,一樣先全給

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private TextView tvStopwatch;
    private StopwatchModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvStopwatch = findViewById(R.id.text_stopwatch);
        Button btStopwatch = findViewById(R.id.button_stopwatch);
        viewModel = new ViewModelProvider(this)
                .get(StopwatchModel.class);

        viewModel.setCallBack(time -> tvStopwatch.setText(time));
        btStopwatch.setOnClickListener(view ->viewModel.stopwatchSwitch());
    }
}

 

一個重點,初始化ViewModel

 

沒了(笑)

下面的部分的話則是callback以及取用方法開啟/結束計時

大概就是如此~


 

這篇文章我自己在寫的時候也覺得有點草草的

不過這當時在寫Code的時候,還是花了點時間操考一下別人的碼表通常怎麼寫

然後再試著把他加入到ViewModel這樣,也就是雖然本文短,不過寫扣花的時間稍微比較多

希望有幫助到大家囉~

最後....

TK

arrow
arrow
    創作者介紹
    創作者 碼農日常 的頭像
    碼農日常

    碼農日常大小事

    碼農日常 發表在 痞客邦 留言(1) 人氣()