在Android中使用LiveData和ViewModel

1膀篮、概述

LiveData是一種可觀察的數(shù)據(jù)存儲器類,LiveData使用觀察者模式,每當數(shù)據(jù)發(fā)生變化時有勾,LiveData會通知 Observer對象,我們可以在這些 Observer 對象中更新UI

ViewModel對象為特定的界面組件(如 Fragment 或 Activity)提供數(shù)據(jù)古程,并包含數(shù)據(jù)處理業(yè)務邏輯蔼卡,會配合LiveData一起使用

接下來,我們會先介紹如果使用LiveData挣磨,并編寫一個LiveData Demo雇逞,接著再結(jié)合ViewModel對LiveData Demo的代碼進行重構(gòu)

2、LiveData使用說明

LiveData<T>是一個抽象類茁裙,它有2個子類分別是:MutableLiveData<T>MediatorLiveData<T>塘砸,在編寫代碼時,是創(chuàng)建的子類呜达。我們先來看MutableLiveData<T>使用方法谣蠢,在后面的示例中再介紹如何使用MediatorLiveData<T>

2.1、創(chuàng)建LiveData對象

如果我們要觀察的對象類為String查近,就通過如下代碼創(chuàng)建一個MutableLiveData對象

MutableLiveData<String> liveData = new MutableLiveData<>();

2.2眉踱、觀察LiveData對象

通過observe方法來監(jiān)聽LiveData的數(shù)據(jù)變化,每當LiveData發(fā)生變化時霜威,都會回調(diào)onChanged方法谈喳,并返回值的內(nèi)容String s

liveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(String s) {
        Log.i(TAG, "onChanged: " + s);
    }
});

2.3、更新LiveData對象

通過LiveDatasetValue方法戈泼,給LiveData賦值婿禽,每次賦完值后赏僧,都會回調(diào)onChanged方法

liveData.setValue("add for test");

當給LiveData賦值為"add for test"時,onChanged會輸出日志:

2021-03-20 21:59:21.483 26430-26430/com.example.livedatademo I/LiveDataDemo: onChanged: add for test

3扭倾、編寫LiveData Demo

我們創(chuàng)建一個Demo淀零,主界面只有一個TextViewTextView用作展示一個數(shù)字膛壹,這個數(shù)字會從59開始顯示驾中,然后每隔1秒數(shù)字會減1,直到數(shù)字變?yōu)?模聋。

Demo運行效果

接下來肩民,我們看下不使用LiveData使用LiveData來實現(xiàn)這個Demo的區(qū)別

3.1、不使用LiveData

  • 代碼說明如下:
    1链方、在MainActivity增加一個countDown的方法持痰,通過CountDownTimer創(chuàng)建一個倒計時器
    2、new CountDownTimer(1 * 60 * 1000, 1 * 1000)中2個參數(shù)的含義:第一個參數(shù)表示這個Timer的總時長為60秒祟蚀;第2個參數(shù)表示每隔1秒中會更新一次工窍,并回調(diào)onTick方法
    3、每次調(diào)用onTick方法暂题,會調(diào)用textView.setText(String.valueOf(l / 1000));設置一次TextView的內(nèi)容

  • 對應代碼如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "LiveDataDemo";

    private TextView textView;

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

        textView = findViewById(R.id.textView);

        countDown();
    }

    private void countDown() {
        new CountDownTimer(1 * 60 * 1000, 1 * 1000) {
            @Override
            public void onTick(long l) {
                Log.i(TAG, "onTick: " + l);
                textView.setText(String.valueOf(l / 1000));
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }
}
  • 弊端:
    業(yè)務邏輯(倒計時的功能)UI邏輯(TextView更新)沒有分開移剪,耦合到一起了

3.2、使用MutableLiveData

為了方便演示薪者,我們直接在MainActivity中操作LiveData,實際項目中不要這樣編寫代碼剿涮,應該把LiveData放到ViewModel中

Google推薦的使用LiveData的方法
  • 代碼說明如下:
    1言津、在MainActivity中增加一個long類型的MutableLiveData
    2、每次回調(diào)onTick方法時取试,調(diào)用liveData.setValue(l);悬槽,把最新的值設置給LiveData
    3、調(diào)用LivewDataobserve方法瞬浓,設置一個監(jiān)聽器初婆,每當LiveData的值變化時,會回調(diào)onChanged方法猿棉,在onChanged中設置TextView的內(nèi)容

  • 對應代碼如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "LiveDataDemo";
    private final MutableLiveData<Long> liveData = new MutableLiveData<>();
    private TextView textView;

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

        textView = findViewById(R.id.textView);

        // TODO:為了方便演示磅叛,我們直接在MainActivity中操作LiveData,實際項目中不要這樣編寫代碼萨赁,應該把LiveData放到ViewModel中
        liveData.observe(this, new Observer<Long>() {
            @Override
            public void onChanged(Long aLong) {
                textView.setText(String.valueOf(aLong / 1000));
            }
        });

        countDown();
    }

    public void countDown() {
        new CountDownTimer(1 * 60 * 1000, 1 * 1000) {
            @Override
            public void onTick(long l) {
                Log.i(TAG, "onTick: " + l);
                // TODO:為了方便演示弊琴,我們直接在MainActivity中操作LiveData,實際項目中不要這樣編寫代碼杖爽,應該把LiveData放到ViewModel中
                liveData.setValue(l);
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }
}

3.3敲董、使用MediatorLiveData

MediatorLiveData可以合并多個LiveData源紫皇,只要任何原始的LiveData源對象發(fā)生更改,就會觸發(fā)MediatorLiveData對象的觀察者腋寨。

例如聪铺,如果界面中有可以從本地數(shù)據(jù)庫或網(wǎng)絡更新的 LiveData 對象,則可以向MediatorLiveData 對象添加以下源:

  • 與存儲在數(shù)據(jù)庫中的數(shù)據(jù)關(guān)聯(lián)的 LiveData 對象萄窜。
  • 與從網(wǎng)絡訪問的數(shù)據(jù)關(guān)聯(lián)的 LiveData 對象计寇。

Activity只需觀察MediatorLiveData對象即可從這兩個源接收更新。接下來脂倦,我們會實現(xiàn)這個例子番宁。

3.3.1、監(jiān)聽2個數(shù)據(jù)源的變化

  • 代碼說明如下:
    1赖阻、創(chuàng)建1個MediatorLiveData<String>對象

    final MediatorLiveData<String> liveDataMerger = new MediatorLiveData<>();
    

    2蝶押、創(chuàng)建2個MutableLiveData:liveData1表示網(wǎng)絡數(shù)據(jù)源,liveData2表示本地數(shù)據(jù)源

    private MutableLiveData<String> liveData1 = new MutableLiveData<>();
    private MutableLiveData<String> liveData2 = new MutableLiveData<>();
    

    3火欧、調(diào)用MediatorLiveDataaddSource方法棋电,分別將liveData1liveData2加到MediatorLiveData要監(jiān)聽的數(shù)據(jù)源中

    liveDataMerger.addSource(liveData1, new Observer<String>() {
      @Override
      public void onChanged(String s) {
          Log.i(TAG, "addSource1 onChanged: " + s);
          liveDataMerger.setValue(s);
      }
    });
    liveDataMerger.addSource(liveData2, new Observer<String>() {
      @Override
      public void onChanged(String s) {
          Log.i(TAG, "addSource2 onChanged: " + s);
          liveDataMerger.setValue(s);
      }
    });
    

    4、設置MediatorLiveData的監(jiān)聽苇侵,當2個數(shù)據(jù)源發(fā)生變化時赶盔,會回調(diào)onChanged方法,并將變化的內(nèi)容展示到TextView

    liveDataMerger.observe(this, new Observer<String>() {
      @Override
      public void onChanged(String s) {
          Log.i(TAG, "liveDataMerger onChanged: " + s);
          textView.setText(s);
      }
    

    });

3.3.2榆浓、 編寫模擬2個數(shù)據(jù)源更新的代碼

編寫一個mergeTes的方法于未,里面創(chuàng)建了2個Timer,用作模擬網(wǎng)絡數(shù)據(jù)更新和本地數(shù)據(jù)更新的情況陡鹃,每隔3秒會往網(wǎng)絡數(shù)據(jù)liveData1設置值烘浦、每隔10秒會往本地數(shù)據(jù)liveData2設置值。對應代碼如下:

public void mergeTest() {
    new CountDownTimer(1 * 60 * 1000, 3 * 1000) {
        @Override
        public void onTick(long l) {
            liveData1.setValue("網(wǎng)絡有數(shù)據(jù)更新了" + l/1000);
        }

        @Override
        public void onFinish() {

        }
    }.start();

    new CountDownTimer(1 * 60 * 1000, 10 * 1000) {
        @Override
        public void onTick(long l) {
            liveData2.setValue("本地數(shù)據(jù)庫更新了" + l/1000);
        }

        @Override
        public void onFinish() {

        }
    }.start();
}

3.3.4萍鲸、MediatorLiveData運行效果

每隔3秒闷叉,TextView更新為如下內(nèi)容:

網(wǎng)絡數(shù)據(jù)有更新

每隔10秒,TextView更新為如下內(nèi)容:

本地數(shù)據(jù)有更新

4脊阴、ViewModel使用

4.1 創(chuàng)建ViewModel類

創(chuàng)建一個MyViewModel類握侧,繼承自ViewModel

import androidx.lifecycle.ViewModel;

public class MyViewMode extends ViewModel  {
}

4.2 在Activity中使用

public class MainActivity extends AppCompatActivity {
    private MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }
}

如果遇到new ViewModelProvider(this)報錯,就在app/build.gradle中加入依賴implementation "androidx.lifecycle:lifecycle-viewmodel:2.3.0"

new ViewModelProvider(this)報錯

5嘿期、結(jié)合ViewModel品擎,重構(gòu)LiveData Demo

5.1、在ViewModel中增加LiveData

在MyViewModel中增加3個MutableLiveData秽五,分別對應在MainActivity中使用到的孽查。并編寫這3個LiveData的get方法

public class MyViewModel extends ViewModel {
    private MutableLiveData<Long> liveData = new MutableLiveData<>();
    private MutableLiveData<String> liveData1 = new MutableLiveData<>();
    private MutableLiveData<String> liveData2 = new MutableLiveData<>();

    public MutableLiveData<Long> getLiveData() {
        return liveData;
    }

    public MutableLiveData<String> getLiveData1() {
        return liveData1;
    }

    public MutableLiveData<String> getLiveData2() {
        return liveData2;
    }
}

5.2、在ViewModel中添加操作數(shù)據(jù)的邏輯

countDown坦喘、mergeTest這2個方法在操作數(shù)據(jù)盲再,從MainActivity中把這2個方法copy到 MyViewModel中

public class MyViewModel extends ViewModel {
    private static final String TAG = "MyViewMode";
    ... ...
    public void countDown() {
        new CountDownTimer(1 * 60 * 1000, 1 * 1000) {
            @Override
            public void onTick(long l) {
                Log.i(TAG, "onTick: " + l);
                liveData.postValue(l);
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }

    public void mergeTest() {
        new CountDownTimer(1 * 60 * 1000, 3 * 1000) {
            @Override
            public void onTick(long l) {
                liveData1.postValue("網(wǎng)絡有數(shù)據(jù)更新了" + l / 1000);
            }

            @Override
            public void onFinish() {

            }
        }.start();

        new CountDownTimer(1 * 60 * 1000, 10 * 1000) {
            @Override
            public void onTick(long l) {
                liveData2.postValue("本地數(shù)據(jù)庫更新了" + l / 1000);
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }
}

5.3 修改MainActivity中和liveData有關(guān)的代碼

1西设、刪除countDown,改用viewModel.countDown();

    viewModel = new ViewModelProvider(this).get(MyViewModel.class);
    viewModel.countDown();

2答朋、用viewModel.getLiveData().observe替換liveData.observe

    viewModel.getLiveData().observe(this, new Observer<Long>() {
        @Override
        public void onChanged(Long aLong) {
            textView.setText(String.valueOf(aLong/1000));
        }
    });

3贷揽、刪除liveData相關(guān)代碼

經(jīng)過上述修改后,liveData相關(guān)的重構(gòu)已完成梦碗,MainActivity代碼如下:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "LiveDataDemo";

    private TextView textView;

    private MutableLiveData<String> liveData1 = new MutableLiveData<>();
    private MutableLiveData<String> liveData2 = new MutableLiveData<>();
    private MyViewModel viewModel;

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

        viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.countDown();

       .... ...
    }

5.4禽绪、修改MyViewModel

增加liveDataMerger字段,并編寫getLiveDataMerger()方法洪规,這個方法里面的內(nèi)容印屁,是從MainActivity中copy過來的

public class MyViewModel extends ViewModel {
    private static final String TAG = "MyViewMode";
    .... ...
    private MediatorLiveData<String> liveDataMerger;

    public MediatorLiveData<String> getLiveDataMerger() {
        if (liveDataMerger == null) {
            liveDataMerger = new MediatorLiveData<>();
        }
        liveDataMerger.addSource(liveData1, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "addSource1 onChanged: " + s);
                liveDataMerger.setValue(s);
            }
        });
        liveDataMerger.addSource(liveData2, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "addSource2 onChanged: " + s);
                liveDataMerger.setValue(s);
            }
        });
        return liveDataMerger;
    }
   ... ...
}

5.5、修改MainActivity中和MediatorLiveData有關(guān)的代碼

1斩例、使用viewModel.getLiveDataMerger().observe替換liveDataMerger.observe

    viewModel.getLiveDataMerger().observe(this, new Observer<String>() {
        @Override
        public void onChanged(String s) {
            Log.i(TAG, "liveDataMerger onChanged: " + s);
            textView.setText(s);
        }
    });

2雄人、使用viewModel.mergeTest();替換mergeTest()

    viewModel.mergeTest();

3、刪除掉liveDataMerger相關(guān)的代碼

5.6 重構(gòu)后MainActivity的代碼

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "LiveDataDemo";

    private TextView textView;
    private MyViewModel viewModel;

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

        viewModel = new ViewModelProvider(this).get(MyViewModel.class);

        viewModel.getLiveData().observe(this, new Observer<Long>() {
            @Override
            public void onChanged(Long aLong) {
                textView.setText(String.valueOf(aLong / 1000));
            }
        });

        viewModel.getLiveDataMerger().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "liveDataMerger onChanged: " + s);
                textView.setText(s);
            }
        });


        viewModel.mergeTest();
        // viewModel.countDown();
    }
}

5.7 重構(gòu)后MyViewModel的代碼

public class MyViewModel extends ViewModel {
    private static final String TAG = "MyViewMode";

    private MutableLiveData<Long> liveData = new MutableLiveData<>();
    private MutableLiveData<String> liveData1 = new MutableLiveData<>();
    private MutableLiveData<String> liveData2 = new MutableLiveData<>();
    private MediatorLiveData<String> liveDataMerger;

    public MediatorLiveData<String> getLiveDataMerger() {
        if (liveDataMerger == null) {
            liveDataMerger = new MediatorLiveData<>();
        }
        liveDataMerger.addSource(liveData1, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "addSource1 onChanged: " + s);
                liveDataMerger.setValue(s);
            }
        });
        liveDataMerger.addSource(liveData2, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "addSource2 onChanged: " + s);
                liveDataMerger.setValue(s);
            }
        });
        return liveDataMerger;
    }

    public MutableLiveData<Long> getLiveData() {
        return liveData;
    }

    public MutableLiveData<String> getLiveData1() {
        return liveData1;
    }

    public MutableLiveData<String> getLiveData2() {
        return liveData2;
    }

    public void countDown() {
        new CountDownTimer(1 * 60 * 1000, 1 * 1000) {
            @Override
            public void onTick(long l) {
                Log.i(TAG, "onTick: " + l);
                liveData.postValue(l);
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }

    public void mergeTest() {
        new CountDownTimer(1 * 60 * 1000, 3 * 1000) {
            @Override
            public void onTick(long l) {
                liveData1.postValue("網(wǎng)絡有數(shù)據(jù)更新了" + l / 1000);
            }

            @Override
            public void onFinish() {

            }
        }.start();

        new CountDownTimer(1 * 60 * 1000, 10 * 1000) {
            @Override
            public void onTick(long l) {
                liveData2.postValue("本地數(shù)據(jù)庫更新了" + l / 1000);
            }

            @Override
            public void onFinish() {

            }
        }.start();
    }
}

6念赶、參考

LiveData 概覽
LiveData
MediatorLiveData
ViewModel 概覽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础钠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叉谜,更是在濱河造成了極大的恐慌旗吁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件停局,死亡現(xiàn)場離奇詭異很钓,居然都是意外死亡,警方通過查閱死者的電腦和手機翻具,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門履怯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裆泳,你說我怎么就攤上這事∧叮” “怎么了工禾?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝗柔。 經(jīng)常有香客問我闻葵,道長,這世上最難降的妖魔是什么癣丧? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任槽畔,我火速辦了婚禮邑飒,結(jié)果婚禮上举娩,老公的妹妹穿的比我還像新娘酷鸦。我一直安慰自己睹逃,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布早直。 她就那樣靜靜地躺著寥假,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霞扬。 梳的紋絲不亂的頭發(fā)上糕韧,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音喻圃,去河邊找鬼萤彩。 笑死,一個胖子當著我的面吹牛斧拍,可吹牛的內(nèi)容都是我干的雀扶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼饮焦,長吁一口氣:“原來是場噩夢啊……” “哼怕吴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起县踢,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤转绷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后硼啤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體议经,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年谴返,在試婚紗的時候發(fā)現(xiàn)自己被綠了煞肾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗓袱,死狀恐怖籍救,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渠抹,我是刑警寧澤蝙昙,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站梧却,受9級特大地震影響奇颠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜放航,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一烈拒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦荆几、人聲如沸吓妆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耿战。三九已至,卻和暖如春焊傅,著一層夾襖步出監(jiān)牢的瞬間剂陡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工狐胎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鸭栖,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓握巢,卻偏偏與公主長得像晕鹊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暴浦,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容