android viewmodel如何使用

嗯,我现在要学习如何在Android中使用ViewModel。我之前听说过ViewModel是用来管理UI相关数据的,这样在配置变化比如屏幕旋转时数据不会丢失。但具体怎么用呢?我需要先回忆一下相关的知识。

首先,ViewModel应该是Android Architecture Components的一部分,对吧?所以可能需要添加相关的依赖库。我记得是在build.gradle文件里添加依赖,比如使用androidx.lifecycle:lifecycle-viewmodel-ktx这样的库。不过具体版本号可能要看当前的项目配置,可能需要查一下最新的版本。

然后,创建ViewModel的步骤是怎样的呢?应该需要新建一个类继承自ViewModel或者AndroidViewModel(如果有需要上下文的话)。比如,假设我要管理一个计数器,可以创建一个CounterViewModel类,里面有一个LiveData或者MutableLiveData来保存计数值。然后在Activity或Fragment中获取这个ViewModel的实例。

不过获取ViewModel实例的方式可能有几种,比如使用ViewModelProvider,或者通过by viewModels()这个委托属性,如果是用Kotlin的话。那在Activity中应该怎么获取呢?可能是在onCreate方法里初始化,通过ViewModelProvider(this).get(CounterViewModel::class.java)这样的方式。或者更简洁的方式是用Kotlin的委托,需要导入相应的库,比如activity-ktx或fragment-ktx,这样可以直接用by viewModels()来延迟初始化ViewModel。

然后,在ViewModel里暴露的数据应该通过LiveData或者StateFlow这样的可观察数据对象,这样UI组件可以观察这些数据的变化并更新界面。比如,在Activity里观察LiveData,当数据变化时更新TextView显示的值。

另外,ViewModel的生命周期是怎样的?它应该和Activity或Fragment的生命周期相关联,当Activity销毁时(比如配置变化导致的销毁),ViewModel不会被销毁,而是会保留,直到对应的Activity真正finish。如果是Fragment的话,可能作用域是Fragment或者Activity,这取决于怎么获取ViewModel实例。

还有,ViewModel里不应该持有Activity或View的引用,避免内存泄漏。如果确实需要Context,可以考虑使用AndroidViewModel,它内部会持有Application的Context。但一般情况下,最好避免使用AndroidViewModel,除非必须需要Application的Context。

那具体的例子,比如实现一个计数器,ViewModel里有一个MutableLiveData的整数,初始化为0。然后在Activity中有一个按钮,点击后增加计数,并在TextView显示当前值。这时候,ViewModel中的方法应该如何处理?比如有一个increment方法,修改MutableLiveData的值。在Activity中,点击按钮时调用ViewModel的increment方法,然后观察LiveData的变化来更新UI。

可能需要注意的地方是,LiveData的观察应该在生命周期处于活跃状态时才会触发,比如在onCreate中开始观察,使用observe方法,并传入LifecycleOwner(通常是Activity或Fragment本身)。这样当界面不可见时,不会接收到不必要的更新。

那在Fragment之间共享数据的话,可能需要使用Activity作用域的ViewModel,这样多个Fragment可以获取同一个ViewModel实例。这时候在Fragment中获取ViewModel时,需要使用activity的ViewModelStoreOwner,比如ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)。

还有关于ViewModel的初始化,如果有参数需要传递的话,可能需要使用ViewModelFactory。比如ViewModel的构造函数有参数,这时候不能直接通过ViewModelProvider获取,而是需要创建一个Factory类,实现ViewModelProvider.Factory接口,然后在创建ViewModel实例时使用这个Factory。

例如,假设CounterViewModel需要一个初始计数值作为构造参数,这时候需要创建一个Factory,在create方法中实例化ViewModel,并传递参数。然后在Activity中使用ViewModelProvider的时候,传入这个Factory的实例。

不过现在可能更方便的方式是使用ViewModel的by viewModels()委托,并且如果有参数的话,可能需要自定义工厂。或者,使用Hilt这样的依赖注入库来简化ViewModel的创建,自动处理Factory的生成。

如果项目中使用Hilt的话,可以通过@HiltViewModel注解来标记ViewModel,并在构造函数中使用@Inject注解,然后在Activity或Fragment中通过@AndroidEntryPoint和by viewModels()来获取实例,这样不需要手动处理Factory。

不过这部分可能涉及到依赖注入的知识,对于刚学习ViewModel的人来说,可能暂时不需要深入。先掌握基础的手动创建ViewModel的方法,再学习其他更简便的方式。

总结一下步骤:

  1. 添加依赖库。
  2. 创建继承自ViewModel的类,管理数据。
  3. 在Activity/Fragment中获取ViewModel实例。
  4. 观察ViewModel中的数据变化,更新UI。
  5. 处理用户交互,调用ViewModel的方法修改数据。
  6. 处理ViewModel的初始化参数(如果有的话)使用Factory。

可能遇到的问题:
• 忘记添加依赖导致类找不到。
• 在ViewModel中持有View或Activity导致内存泄漏。
• 没有正确观察LiveData,导致UI不更新。
• 在Fragment中获取ViewModel的作用域不正确,导致不同实例。
• 需要传递构造参数时没有使用Factory,导致崩溃。

现在我需要写一个简单的示例代码,来验证这些步骤是否正确。例如,一个计数器应用,ViewModel保存计数值,点击按钮增加数值,并在TextView显示。屏幕旋转后数值保持。

比如,CounterViewModel类:

class CounterViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }
}

然后在Activity中:

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: CounterViewModel

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

        viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)

        viewModel.count.observe(this) { count ->
            findViewById<TextView>(R.id.textView).text = count.toString()
        }

        findViewById<Button>(R.id.button).setOnClickListener {
            viewModel.increment()
        }
    }
}

这样应该可以正常工作。当屏幕旋转时,ViewModel不会被销毁,计数值保留。

如果使用Kotlin的委托属性,可以更简洁:

import androidx.activity.viewModels

class MainActivity : AppCompatActivity() {
    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...同上,直接使用viewModel
    }
}

这样就不用显式调用ViewModelProvider了。

如果ViewModel需要参数,比如初始值,就需要Factory:

class CounterViewModel(startingCount: Int) : ViewModel() { ... }

class CounterViewModelFactory(private val startingCount: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(CounterViewModel::class.java)) {
            return CounterViewModel(startingCount) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

然后在Activity中:

val factory = CounterViewModelFactory(10)
viewModel = ViewModelProvider(this, factory).get(CounterViewModel::class.java)

或者在委托中使用:

private val viewModel: CounterViewModel by viewModels { CounterViewModelFactory(10) }

这样初始计数值就是10。

不过现在在Hilt中,可以使用@AssistedInject和SavedStateHandle来处理这种情况,但这是更高级的内容了。

可能需要注意的还有,ViewModel中的LiveData应该使用postValue如果在后台线程更新的话,但如果是通过按钮点击触发,点击事件在主线程,所以直接设置value即可。

总结来说,ViewModel的使用步骤包括正确创建类,获取实例,观察数据,处理交互,以及可能的参数传递。需要避免常见的错误,如内存泄漏和作用域错误。

在Android中使用ViewModel可以有效管理UI相关数据,并在配置更改(如屏幕旋转)时保持数据状态。以下是详细步骤和示例代码:

步骤1:添加依赖

build.gradle中添加Lifecycle组件依赖:

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
    implementation "androidx.activity:activity-ktx:1.7.0"
}

步骤2:创建ViewModel类

继承ViewModel,管理数据(如计数器):

class CounterViewModel : ViewModel() {
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }
}

步骤3:在Activity/Fragment中获取ViewModel

使用by viewModels()委托简化初始化:

import androidx.activity.viewModels

class MainActivity : AppCompatActivity() {
    private val viewModel: CounterViewModel by viewModels()

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

        // 观察LiveData并更新UI
        viewModel.count.observe(this) { currentCount ->
            findViewById<TextView>(R.id.tv_count).text = currentCount.toString()
        }

        // 按钮点击增加计数
        findViewById<Button>(R.id.btn_increment).setOnClickListener {
            viewModel.increment()
        }
    }
}

步骤4:处理构造参数(使用Factory)

若ViewModel需要参数,创建自定义ViewModelProvider.Factory

class CustomViewModel(startingCount: Int) : ViewModel() {
    private val _count = MutableLiveData(startingCount)
    // ...其他代码同CounterViewModel
}

class CustomViewModelFactory(private val startingCount: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(CustomViewModel::class.java)) {
            return CustomViewModel(startingCount) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

在Activity中使用Factory:

class MainActivity : AppCompatActivity() {
    private val viewModel: CustomViewModel by viewModels { CustomViewModelFactory(10) }
    // 其余代码同上
}

关键点总结

生命周期感知:ViewModel自动关联Activity/Fragment生命周期,避免数据丢失。
数据观察:通过LiveDataStateFlow观察数据变化,确保UI更新仅在活跃状态进行。
作用域控制
• Activity作用域:同一Activity的多个Fragment共享同一ViewModel实例(使用requireActivity()获取)。
• Fragment作用域:每个Fragment拥有独立实例。
避免内存泄漏:ViewModel内不持有Activity/View引用,需Context时使用AndroidViewModel(提供Application Context)。

示例代码(Fragment间共享数据)

SharedViewModel

class SharedViewModel : ViewModel() {
    val sharedData = MutableLiveData<String>()
}

FragmentA

class FragmentA : Fragment() {
    private val sharedViewModel: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        sharedViewModel.sharedData.value = "Hello from FragmentA"
    }
}

FragmentB

class FragmentB : Fragment() {
    private val sharedViewModel: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        sharedViewModel.sharedData.observe(viewLifecycleOwner) { data ->
            // 更新UI显示共享数据
        }
    }
}

注意事项

线程安全:在后台线程更新LiveData需用postValue(),主线程直接用setValue()
依赖注入:考虑使用Hilt简化ViewModel创建(需添加hilt-androidhilt-compiler依赖)。

通过以上步骤,可有效利用ViewModel管理UI数据,提升应用健壮性和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/984761.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mac本地部署Qwq-32b记录

导语 昨天看到阿里开源了Qwq-32b&#xff0c;号称性能可以媲美Deepseek-R1。今天晚上有空就在Mac上折腾了一下&#xff0c;使用ollma进行了部署&#xff0c;效果感觉还不错&#xff0c;特此记录。 环境 硬件 型号&#xff1a;Macbook M1 Pro 14寸内存&#xff1a;512G 环境…

Vue3 Pinia 符合直觉的Vue.js状态管理库

Pinia 符合直觉的Vue.js状态管理库 什么时候使用Pinia 当两个关系非常远的组件&#xff0c;要传递参数时使用Pinia组件的公共参数使用Pinia

JAVA面试_进阶部分_深入理解socket网络异常

在各种网络异常情况的背后&#xff0c;TCP是怎么处理的&#xff1f;又是怎样把处理结果反馈给上层应用的&#xff1f;本文就来讨论这个问题。 分为两个场景来讨论 建立连接 1 正常情况下 进过三次握手&#xff0c;客户端连接成功&#xff0c;服务端有一个新连接到来。 2 客…

警惕AI神话破灭:深度解析大模型缺陷与禁用场景指南

摘要 当前AI大模型虽展现强大能力&#xff0c;但其本质缺陷可能引发系统性风险。本文从认知鸿沟、数据困境、伦理雷区、技术瓶颈四大维度剖析大模型局限性&#xff0c;揭示医疗诊断、法律决策等8类禁用场景&#xff0c;提出可信AI建设框架与用户防护策略。通过理论分析与实操案…

《用Python+PyGame开发双人生存游戏!源码解析+完整开发思路分享》

导语​ "你是否想过用Python开发一款可玩性高的双人合作游戏&#xff1f;本文将分享如何从零开始实现一款类《吸血鬼幸存者》的生存射击游戏&#xff01;包含完整源码解析、角色系统设计、敌人AI逻辑等核心技术点&#xff0c;文末提供完整代码包下载&#xff01;" 哈…

Python实例:PyMuPDF实现PDF翻译,英文翻译为中文,并按段落创建中文PDF

基于PyMuPDF与百度翻译的PDF翻译处理系统开发:中文乱码解决方案与自动化排版实践 一 、功能预览:将英文翻译为中文后创建的PDF 二、完整代码 from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

华为hcia——Datacom实验指南——三层交换和ARP的工作原理

什么是三层交换 三层交换是指连接在同一台三层交换机上&#xff0c;不同vlan用户&#xff0c;不同网段ip&#xff0c;通过vlanif接口进行数据交换。 什么是ARP协议 通过网络层的ip地址解析成数据链路层的mac地址。 说白了就是通过目标ip地址去问他对应的mac地址是多少。 A…

YC 孵化项目 Pinch:实时语音翻译视频会议平台;Mistral OCR:能处理多语言多模态复杂文档丨日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。 我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 技术 」、「有亮点的 产品 」、「有思考的 文章 」、「有态度的 …

github生成badges的方法

在Github页面上生成类似下面这样的badge的方法 你可以通过以下步骤在GitHub个人主页的README中创建类似的技术栈徽章&#xff1a; 一、使用 Shields.io 生成徽章 Shields.io 是一个开源徽章生成工具&#xff0c;支持自定义文本、颜色、图标等参数。 1. 基础模板 https://…

Microsof Visual Studio Code 安装教程(中文设置)

VS Code 是一个免费的代码编辑器&#xff0c;可在 macOS、Linux 和 Windows作系统上运行。启动和运行 VS Code 既快速又简单。VS Code&#xff08;全称 Visual Studio Code&#xff09;是一款由Microsoft 推出的免费、开源、跨平台的代码编辑器&#xff0c;拥有强大的功能和灵活…

实验二:Git用法练习:使用 DevStar 搭建 Git 仓库托管平台

Git 是目前最流行的版本控制工具&#xff0c;而 GitHub、GitLab 等托管平台为团队协作提供了极大的便利。但在某些情况下&#xff0c;我们需要自建 Git 仓库托管平台&#xff0c;例如&#xff1a; 保护私有代码&#xff0c;避免托管到公网 提供企业或学校内部协作 提升对 Git…

一二三应用开发平台——能力扩展:多数据源支持

背景 随着项目规模的扩大&#xff0c;单一数据源已无法满足复杂业务需求&#xff0c;多数据源应运而生。 技术选型 MyBatis-Plus 的官网提供了两种多数据源扩展插件&#xff1a;开源生态的 <font style"color:rgb(53, 56, 65);">dynamic-datasource</fon…

【性能测试】Jmeter下载安装、环境配置-小白使用手册(1)

本篇文章主要包含Jmeter的下载安装、环境配置 添加线程组、结果树、HTTP请求、请求头设置。JSON提取器的使用&#xff0c;用户自定义变量 目录 一&#xff1a;引入 1&#xff1a;软件介绍 2&#xff1a;工作原理 3&#xff1a;安装Jmeter 4&#xff1a;启动方式 &#xf…

每日一练之链表的回文结构

题目描述&#xff1a; 方法一&#xff1a; 找到链表的中间结点。 图片解疑&#xff1a; 从中间结点开始把后面的链表反转。注&#xff1a;详细请看附录链接 图片解疑&#xff1a; 判断头结点的值和尾结点的值是否一样。 代码实例&#xff1a; /* struct ListNode {int val;str…

WinUI 3 支持的三种窗口 及 受限的窗口透明

我的目标 希望能够熟悉 WinUI 3 窗口的基本使用方式&#xff0c;了解可能出现的问题 。 WinUI 3 支持三种窗口模式&#xff0c;分别为&#xff1a;常规窗口模式、画中画模式、全屏模式。 窗口模式&#xff1a;常规 即我们最常见的普通窗口。 支持&#xff1a;显示最大化按钮…

恶劣天候三维目标检测论文列表整理

恶劣天候三维目标检测论文列表 图摘自Kradar &#x1f3e0; 介绍 Hi&#xff0c;这是有关恶劣天气下三维目标检测的论文列表。主要是来源于近3年研究过程中认为有意义的文章。希望能为新入门的研究者提供一些帮助。 可能比较简陋&#xff0c;存在一定的遗漏&#xff0c;欢迎…

SSL VXN

SSL VPN是采用SSL&#xff08;Security Socket Layer&#xff09;/TLS&#xff08;Transport Layer Security&#xff09;协议来实现远程接入的一种轻量级VPN技术,其基于B/S架构&#xff0c;免于安装客户端&#xff0c;相较与IPSEC有更高的灵活度和管理性&#xff0c;当隧道建立…

使用 AIStor、MLflow 和 KServe 将模型部署到 Kubernetes

在之前几篇关于 MLOps 工具的文章中&#xff0c;我展示了有多少流行的 MLOps 工具跟踪与模型训练实验相关的指标。我还展示了他们如何使用 MinIO 来存储作为模型训练管道一部分的非结构化数据。但是&#xff0c;一个好的 MLOps 工具应该做的不仅仅是管理您的实验、数据集和模型…

Python数据分析之数据处理与分析

Python 数据分析重点知识点 本系列不同其他的知识点讲解&#xff0c;力求通过例子让新同学学习用法&#xff0c;帮助老同学快速回忆知识点 可视化系列&#xff1a; Python基础数据分析工具数据处理与分析数据可视化机器学习基础 三、数据处理与分析 数据清洗 处理缺失值&a…

第六次CCF-CSP认证(含C++源码)

第六次CCF-CSP认证 数位之和&#xff08;easy&#xff09;思路及AC代码遇到的问题 开心消消乐&#xff08;easy&#xff09;思路及AC代码 画图&#xff08;mid&#xff09;思路及AC代码 数位之和&#xff08;easy&#xff09; 题目链接 思路及AC代码 既然题目要求我们输出各位…