android mvvm實例解析

来源:https://www.cnblogs.com/ttylinux/archive/2023/08/04/17607261.html
-Advertisement-
Play Games

本文分享自華為雲社區《如何為物聯網設備註入“華為雲+鴻蒙DNA”?看華為雲IoT怎麼答【華為雲IoT +鴻蒙】》,作者: 華為IoT雲服務。 根據市場咨詢機構預測,2025年全球物聯網設備將達到252億個。但各種智能設備大多都有一套自己的系統,而且互相“孤立”,無法交流。鴻蒙的到來,就是要用同一套語 ...


MVVM架構,將整個應用分為三層,View層,VM層,Model層。其中View層單向引用VM層,VM層單向引用Model層。如上圖。 單向引用,而非雙向引用,這是MVVM與MVP最大的區別。View層,只是單向引用VM層,VM層不需要引用View層,但是卻可以 更新View層。這是通過VM層的觀察者模式實現的,在這裡使用架構組件LiveData,觀察者註冊LiveData,當LiveData數據發生變更 的時候,就會通知註冊的觀察者。 VM層,執行業務邏輯,獲取Model層的數據,Model層的數據由repository來提供。   舉例子: ChooseAreaFragment是View層,它持有ViewModel,它可以監聽相關數據,相關數據發生變化的時候,對應的UI就會被更新。 比如:dataChanged數據發生變化,就會執行定義的觀察者操作。   viewModel.dataChanged.observe(this, Observer {             adapter.notifyDataSetChanged()             listView.setSelection(0)             closeProgressDialog()         })
class ChooseAreaFragment : Fragment() {


    private val viewModel by lazy { ViewModelProviders.of(this, InjectorUtil.getChooseAreaModelFactory()).get(ChooseAreaViewModel::class.java) }
    private var progressDialog: ProgressDialog? = null
    private lateinit var adapter: ArrayAdapter<String>


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.choose_area, container, false)
        val binding = DataBindingUtil.bind<ChooseAreaBindingImpl>(view)
        binding?.viewModel = viewModel
        return view
    }


    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        adapter = ChooseAreaAdapter(context!!, R.layout.simple_item, viewModel.dataList)
        listView.adapter = adapter
        observe()
    }


    private fun observe() {
        viewModel.currentLevel.observe(this, Observer { level ->
            when (level) {
                LEVEL_PROVINCE -> {
                    titleText.text = "中國"
                    backButton.visibility = View.GONE
                }
                LEVEL_CITY -> {
                    titleText.text = viewModel.selectedProvince?.provinceName
                    backButton.visibility = View.VISIBLE
                }
                LEVEL_COUNTY -> {
                    titleText.text = viewModel.selectedCity?.cityName
                    backButton.visibility = View.VISIBLE
                }
            }
        })


        viewModel.dataChanged.observe(this, Observer {
            adapter.notifyDataSetChanged()
            listView.setSelection(0)
            closeProgressDialog()
        })
        viewModel.isLoading.observe(this, Observer { isLoading ->
            if (isLoading) showProgressDialog()
            else closeProgressDialog()
        })
        viewModel.areaSelected.observe(this, Observer { selected ->
            if (selected && viewModel.selectedCounty != null) {
                if (activity is MainActivity) {
                    val intent = Intent(activity, WeatherActivity::class.java)
                    intent.putExtra("weather_id", viewModel.selectedCounty!!.weatherId)
                    startActivity(intent)
                    activity?.finish()
                } else if (activity is WeatherActivity) {
                    val weatherActivity = activity as WeatherActivity
                    weatherActivity.drawerLayout.closeDrawers()
                    weatherActivity.viewModel.weatherId = viewModel.selectedCounty!!.weatherId
                    weatherActivity.viewModel.refreshWeather()
                }
                viewModel.areaSelected.value = false
            }
        })
        if (viewModel.dataList.isEmpty()) {
            viewModel.getProvinces()
        }
    }


    /**
     * 顯示進度對話框
     */
    private fun showProgressDialog() {
        if (progressDialog == null) {
            progressDialog = ProgressDialog(activity)
            progressDialog?.setMessage("正在載入...")
            progressDialog?.setCanceledOnTouchOutside(false)
        }
        progressDialog?.show()
    }


    /**
     * 關閉進度對話框
     */
    private fun closeProgressDialog() {
        progressDialog?.dismiss()
    }


    companion object {
        const val LEVEL_PROVINCE = 0
        const val LEVEL_CITY = 1
        const val LEVEL_COUNTY = 2
    }


}
VM層,ViewModel: 使用LiveData包裝被View層監聽的數據,在VM層數據發生的變化,會通知到View層,但卻無需要View層的引用。 因為LiveData應用了觀察者模式,註冊的觀察者,在數據發生變化的時候,會自動通知觀察者。 如下,currentLevel,dataChanged,isLoading等都是使用LiveData包裝的,意味著,它們發生變化的時候View層會監聽得到,從而進行相應的更新操作。 在VM層,持有Model層的引用,Model層的數據獲取,網路請求,都依賴repository實現。
class ChooseAreaViewModel(private val repository: PlaceRepository) : ViewModel() {


    var currentLevel = MutableLiveData<Int>()


    var dataChanged = MutableLiveData<Int>()


    var isLoading = MutableLiveData<Boolean>()


    var areaSelected = MutableLiveData<Boolean>()


    var selectedProvince: Province? = null


    var selectedCity: City? = null


    var selectedCounty: County? = null


    lateinit var provinces: MutableList<Province>


    lateinit var cities: MutableList<City>


    lateinit var counties: MutableList<County>


    val dataList = ArrayList<String>()


    fun getProvinces() {
        currentLevel.value = LEVEL_PROVINCE
        launch {
            provinces = repository.getProvinceList()
            dataList.addAll(provinces.map { it.provinceName })
        }
    }


    private fun getCities() = selectedProvince?.let {
        currentLevel.value = LEVEL_CITY
        launch {
            cities = repository.getCityList(it.provinceCode)
            dataList.addAll(cities.map { it.cityName })
        }
    }


    private fun getCounties() = selectedCity?.let {
        currentLevel.value = LEVEL_COUNTY
        launch {
            counties = repository.getCountyList(it.provinceId, it.cityCode)
            dataList.addAll(counties.map { it.countyName })
        }
    }


    fun onListViewItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
        when {
            currentLevel.value == LEVEL_PROVINCE -> {
                selectedProvince = provinces[position]
                getCities()
            }
            currentLevel.value == LEVEL_CITY -> {
                selectedCity = cities[position]
                getCounties()
            }
            currentLevel.value == LEVEL_COUNTY -> {
                selectedCounty = counties[position]
                areaSelected.value = true
            }
        }
    }


    fun onBack() {
        if (currentLevel.value == LEVEL_COUNTY) {
            getCities()
        } else if (currentLevel.value == LEVEL_CITY) {
            getProvinces()
        }
    }


    private fun launch(block: suspend () -> Unit) = viewModelScope.launch {
        try {
            isLoading.value = true
            dataList.clear()
            block()
            dataChanged.value = dataChanged.value?.plus(1)
            isLoading.value = false
        } catch (t: Throwable) {
            t.printStackTrace()
            Toast.makeText(CoolWeatherApplication.context, t.message, Toast.LENGTH_SHORT).show()
            dataChanged.value = dataChanged.value?.plus(1)
            isLoading.value = false
        }
    }


}
Model層: 在這個例子中,Model層對外提供的方法是 getProvinceList,getCityList,getCountyList。 它的數據來源,可能是資料庫Dao,或者是網路,各自的實現,再委托到具體的方法去實現。
class PlaceRepository private constructor(private val placeDao: PlaceDao, private val network: CoolWeatherNetwork) {


    suspend fun getProvinceList() = withContext(Dispatchers.IO) {
        var list = placeDao.getProvinceList()
        if (list.isEmpty()) {
            list = network.fetchProvinceList()
            placeDao.saveProvinceList(list)
        }
        list
    }


    suspend fun getCityList(provinceId: Int) = withContext(Dispatchers.IO) {
        var list = placeDao.getCityList(provinceId)
        if (list.isEmpty()) {
            list = network.fetchCityList(provinceId)
            list.forEach { it.provinceId = provinceId }
            placeDao.saveCityList(list)
        }
        list
    }


    suspend fun getCountyList(provinceId: Int, cityId: Int) = withContext(Dispatchers.IO) {
        var list = placeDao.getCountyList(cityId)
        if (list.isEmpty()) {
            list = network.fetchCountyList(provinceId, cityId)
            list.forEach { it.cityId = cityId }
            placeDao.saveCountyList(list)
        }
        list
    }


    companion object {


        private var instance: PlaceRepository? = null


        fun getInstance(placeDao: PlaceDao, network: CoolWeatherNetwork): PlaceRepository {
            if (instance == null) {
                synchronized(PlaceRepository::class.java) {
                    if (instance == null) {
                        instance = PlaceRepository(placeDao, network)
                    }
                }
            }
            return instance!!
        }


    }


}
以上就是MVVM的實例解析。應用MVVM的時候,關鍵是劃分功能屬於哪一個層次,然後,再確定引用關係。劃分功能屬於哪個層次,可以依據單一職責原則,讓功能代碼原子化,再在這一基礎上去區分層次。     版權聲明: 作者:ttylinux     出處:http://www.cnblogs.com/ttylinux/     本文版權歸作者,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、Linux下本地yum源配置 本地yum源依賴於python解析,首先要確保系統的python和yum源安裝完成 1.1、本地yum源配置及掛載 上傳ISO鏡像或使用本機鏡像,使用mount命令掛載鏡像到/dev/loop0設備文件(用於模擬塊設備) mount /dev/loop0 /opt/ ...
  • 適用Linux所有版本,就是命令不一樣,我以Ubuntu為例,命令使用的也是Ubuntu的。 sudo apt-get install bcmwl-kernel-source #Broadcom 802.11 Linux STA 無線驅動源 sudo apt-get install broadcom ...
  • 1.確認nginx是否已安裝SSL模塊 查驗方法:進入sbin目錄,執行以下語句,顯示結果如標記所示則表示安裝成功 ./nginx -V 2.確認系統以安裝SSL工具,開始製作證書 選擇一個存放證書的路徑,執行以下語句即可: (1)生成密鑰,得到文件private.key openssl genpk ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230803123612359-1050633424.png) # 1. 問題9 ## 1.1. 只講授一門課程的教授 ## 1.2. sql ```sql select p. ...
  • **原文鏈接:** [使用 RediSearch 在 Redis 中進行全文檢索](https://mp.weixin.qq.com/s/X1qKL0jMaklGw6GLcrkp2g) Redis 大家肯定都不陌生了,作為一種快速、高性能的鍵值存儲資料庫,廣泛應用於緩存、隊列、會話存儲等方面。 然而 ...
  • #### 第3句 今日流失用戶 ##### 需求: 當日流失用戶的定義:昨天登錄的,今天沒登錄的用戶數 有一張用戶登錄日誌表,有欄位 date_stamp(日期時間戳),用戶id(uid)。如果用戶在某天登錄了,該表會有一條記錄。 ``` #今天流失人數:昨天登錄,今天沒登錄的 select a.d ...
  • TopSQL為DWS的監控系統,記錄DWS中各個作業、運算元級別的資源使用數據、耗時數據,包括下盤信息、記憶體、網路、耗時、警告、基礎信息等作業執行的數據。 ...
  • ![file](https://img2023.cnblogs.com/other/2685289/202308/2685289-20230803180034435-79319118.png) ## 導讀 國內某頭部券商是國內排名前三的全國性大型綜合證券公司。作為證券行業領頭羊之一,該券商一直高度重 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...