Android ViewModel 接收 Intent 参数

通常我们会将页面中的参数通过 ViewModel 来管理,上一页面 Intent 传过来的数据,应直接存入 ViewModel 中,防止 Activity 和 ViewModel 各存一份。

方法一: Factory(经典方式)

kotlin 复制代码
// MainActivity.kt
fun jump() {
   val intent = Intent(this, SecondActivity::class.java)
   intent.putExtra("userId", "tomcat")
   startActivity(intent)
}
kotlin 复制代码
// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    private val viewModel: SecondViewModel by viewModels {
        val userId = intent.getStringExtra("userId") ?: ""
        SecondViewModel.SecondViewModelFactory(userId)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val textView: TextView = findViewById(R.id.tv_res)
        textView.text = viewModel.userId
    }
}

class SecondViewModel(
    val userId: String,
) : ViewModel() {

    init {
        Log.d("Debug", "userId: " + userId)
    }

    class SecondViewModelFactory(
        private val userId: String
    ) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(SecondViewModel::class.java)) {
                return SecondViewModel(userId) as T
            }
            return super.create(modelClass)
        }
    }
}

Lifecycle 2.5+, AndroidX 更推荐使用 viewModelFactory {} 创建 viewModel Factory,

kotlin 复制代码
    private val viewModel: SecondViewModel by viewModels {
        val userId = intent.getStringExtra("userId") ?: ""
//        SecondViewModel.SecondViewModelFactory(userId)
        viewModelFactory {
            initializer {
                SecondViewModel(userId)
            }
        }
    }

方法二:SavedStateHandle(官方推荐)

如果参数来自导航或 Intent,可以使用 SavedStateHandle:参数还能在进程被系统回收后恢复

kotlin 复制代码
// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    val viewModel: SecondViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val textView: TextView = findViewById(R.id.tv_res)
        textView.text = viewModel.userId
    }
}

class SecondViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val userId = savedStateHandle["userId"] as? String
}

SavedStateHandle 为什么能拿到 Intent 中的数据?

对于使用 by viewModels() 创建的 ComponentActivity,默认的 ViewModel 工厂会把 Intent 的 extras(首次创建时)作为 SavedStateHandle 的默认值,因此可以直接通过相同的 key 获取。

方法三:ViewModel 除了 Intent 参数,还有 Repository、UseCase 等依赖

不能再使用默认的 by viewModels() 了。因为默认 Factory 只知道怎么创建 SavedStateHandle,但是不知道 usecase, repository 从哪里来。 因此必须自己实现 Factory。

kotlin 复制代码
class SecondActivity : AppCompatActivity() {
    private val viewModel: SecondViewModel by viewModels {
        viewModelFactory {
            initializer {
                SecondViewModel(
                    getUserIdUseCase = GetUserIdUseCase(),
                    savedStateHandle = createSavedStateHandle(),
                )
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val textView2: TextView = findViewById(R.id.tv_res)
        textView2.text =  "${viewModel.userId} - ${viewModel.userName}"
    }
}

class SecondViewModel(
    private val getUserIdUseCase: GetUserIdUseCase,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    val userId = savedStateHandle["userId"] as? String
    val userName = getUserIdUseCase()
}

class GetUserIdUseCase {
    operator fun invoke(): String {
        return "UseCase data"
    }
}

方法四: Koin 注入

kotlin 复制代码
// App.kt
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            modules(AppModule)
        }
    }
}

val AppModule = module {
    factoryOf<GetUserIdUseCase>(::GetUserIdUseCase)
    viewModel { params ->
        SecondViewModel(getUserIdUseCase = get(), savedStateHandle = get(), greet = params[0], greet2 = params[1])
    }
}
kotlin 复制代码
// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    /**
     *  viewModel(): Koin 提供的 ComponentActivity 的 扩展函数
     */
    private val viewModel: SecondViewModel by viewModel {
        parametersOf("hello", "English")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val textView: TextView = findViewById(R.id.tv_res)
        textView.text = "${viewModel.userId} - ${viewModel.userName} - ${viewModel.greet} - ${viewModel.language}"
    }
}

class SecondViewModel(
    private val getUserIdUseCase: GetUserIdUseCase,
    private val savedStateHandle: SavedStateHandle,
    val greet: String,
    val language: String
) : ViewModel() {

    init {
        Log.d("Debug", "greet: ${greet}")
    }

    val userId = savedStateHandle["userId"] as? String
    val userName = getUserIdUseCase()
}

Koin 对于同类型参数,是按照构造器顺序消费 parametersOf() 中的值, 即:

kotlin 复制代码
parametersOf(
    "hello",
    "English"
)

对应

kotlin 复制代码
greet  = "hello"
language = "English"

如果写反了:

kotlin 复制代码
parametersOf(
    "English",
    "hello"
)

那么就是:

kotlin 复制代码
greet  = "English"
language = "hello"

Koin 不知道哪个 String 是 greet,哪个是 language,只能按顺序匹配, 可读性一般。 可以考虑将参数封装成一个数据类, 如下:

kotlin 复制代码
// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    /**
     *  viewModel(): Koin 提供的 ComponentActivity 的 扩展函数
     */
    private val viewModel: SecondViewModel by viewModel {
        parametersOf(Args(greet = "hello", language = "English"))
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val textView: TextView = findViewById(R.id.tv_res)
        textView.text = "${viewModel.userId} - ${viewModel.userName} - ${viewModel.args.language} - ${viewModel.args.greet}"
    }
}

class SecondViewModel(
    private val getUserIdUseCase: GetUserIdUseCase,
    private val savedStateHandle: SavedStateHandle,
     val args: Args,
//    val greet: String,
//    val language: String
) : ViewModel() {

    init {
        Log.d("Debug", "greet: ${args.greet}")
    }

    val userId = savedStateHandle["userId"] as? String
    val userName = getUserIdUseCase()
}

data class Args(
    val greet: String,
    val language: String
)
相关推荐
吱吱鼠3 分钟前
四、搞懂文件存储管理:多级索引寻道、位示图秒级分配与访问控制体系
架构
吱吱鼠11 分钟前
三、搞懂文件系统的物理存储结构:连续、链式、索引与多级索引
架构
zhangjw341 小时前
1-3 PostgreSQL 架构模型:实例、数据库、Schema 模式、表空间完整概念
数据库·postgresql·架构
软件工程师文艺1 小时前
从微服务到 Serverless:OAM 模型如何统一应用描述与交付
微服务·架构·serverless
吴建旭 智宅焕1 小时前
智能家居全国交付中B端客户的交付差异化能力架构:从低客单价依赖到交付确定性基础设施
架构·智能家居
HRaitest2 小时前
拆解AI招聘系统技术架构:从ATS插件到L3级全自主寻访智能体的选型指南
大数据·人工智能·架构·求职招聘
各类产品分享2 小时前
AR 辅助巡检系统落地:技术架构、工作流设计与实施局限
架构·ar·ar巡检
weixin_750330233 小时前
OPC一人公司AI服务商选型:智能体vs工具集合架构对比
人工智能·架构·opc一人公司
硅基手札3 小时前
SPI技术系列 08 · SPI 控制器寄存器与 IP 架构
驱动开发·stm32·单片机·嵌入式硬件·mcu·架构·计算机外设
Dawson Zhu3 小时前
【AI Infra小白入门⑧】PyTorch框架Infra实战指南:Tensor、autograd与性能调试
人工智能·语言模型·架构·aigc·agi