在 FlutterActivity 中使用 ViewModel 是一个结合了 Flutter 和 Android 原生开发(Android Jetpack)的常见场景。这通常用于需要在 Flutter 和原生 Android 代码之间共享状态或逻辑的情况。
核心思路是:
- 在 Android 原生端(即在 FlutterActivity 或其附属的 Fragment/Activity 中)创建和管理一个 Android ViewModel。
- 通过 MethodChannel 在 Flutter 和原生端之间建立通信桥梁。
- Flutter 端通过 MethodChannel 发送指令或请求数据。
- 原生端的 ViewModel 处理业务逻辑,并可以通过多种方式(如 LiveData、Flow、回调等)将数据返回给 Flutter。
下面我们通过一个详细的示例来实现这个过程。我们将创建一个简单的计数器,其数据源是 Android 端的 ViewModel。
步骤 1:在 Android 原生端设置
首先,确保你的 android/app/build.gradle 文件中添加了 AndroidX 和 Lifecycle 依赖(对于新项目,这些通常已默认包含)。
dependencies {
def lifecycle_version = "2.6.2" // 使用最新稳定版
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// ... 其他依赖
}
- 创建 Android ViewModel
在 android/app/src/main/kotlin/…/ 你的包名下,创建一个 ViewModel 类。
CounterViewModel.kt
package com.yourcompany.yourapp // 替换为你的包名
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
// 使用 LiveData 来持有计数器的状态,以便 Flutter 端观察
private val _counter = MutableLiveData<Int>(0)
val counter: LiveData<Int> = _counter
fun increment() {
_counter.value = (_counter.value ?: 0) + 1
}
fun decrement() {
_counter.value = (_counter.value ?: 0) - 1
}
fun getCurrentCount(): Int {
return _counter.value ?: 0
}
}
- 在 FlutterActivity 中集成 ViewModel 和 MethodChannel
接下来,你需要创建一个自定义的 FlutterActivity 来托管 ViewModel 并处理来自 Flutter 的调用。
MainActivity.kt
package com.yourcompany.yourapp // 替换为你的包名
import androidx.lifecycle.ViewModelProvider
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
// 定义 MethodChannel 的名称,确保与 Flutter 端一致
private val CHANNEL = "samples.flutter.dev/native_counter"
// 延迟初始化 ViewModel
private lateinit var counterViewModel: CounterViewModel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 初始化 ViewModel
counterViewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
// 设置 MethodChannel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
// 处理来自 Flutter 的方法调用
when (call.method) {
"increment" -> {
counterViewModel.increment()
result.success(null) // 通知 Flutter 调用成功
}
"decrement" -> {
counterViewModel.decrement()
result.success(null)
}
"getCurrentCount" -> {
val currentCount = counterViewModel.getCurrentCount()
result.success(currentCount) // 将当前计数返回给 Flutter
}
else -> {
result.notImplemented()
}
}
}
// (可选) 如果你想主动从原生端推送数据到 Flutter(例如使用 EventChannel 监听 LiveData)
// 这部分更复杂,通常用 MethodChannel 的 “调用-返回” 模式就够了。
}
}
重要: 确保你的 AndroidManifest.xml 使用的是这个自定义的 MainActivity。 android/app/src/main/AndroidManifest.xml
<activity
android:name=".MainActivity" <!-- 确保是这个 -->
android:exported="true"
android:launchMode="singleTop"
... >
...
</activity>
步骤 2:在 Flutter 端设置
现在,在 Flutter 代码中,你需要创建一个对应的 MethodChannel 来与原生端通信。
- 创建 MethodChannel 工具类
native_counter_service.dart
import 'package:flutter/services.dart';
class NativeCounterService {
// 与原生端一致的 Channel 名称
static const _channel = MethodChannel('samples.flutter.dev/native_counter');
// 调用原生端的 increment 方法
static Future<void> increment() async {
try {
await _channel.invokeMethod('increment');
} on PlatformException catch (e) {
print("Failed to increment: '${e.message}'.");
}
}
// 调用原生端的 decrement 方法
static Future<void> decrement() async {
try {
await _channel.invokeMethod('decrement');
} on PlatformException catch (e) {
print("Failed to decrement: '${e.message}'.");
}
}
// 调用原生端的 getCurrentCount 方法并获取返回值
static Future<int> getCurrentCount() async {
try {
final int count = await _channel.invokeMethod('getCurrentCount');
return count;
} on PlatformException catch (e) {
print("Failed to get count: '${e.message}'.");
return 0; // 出错时返回默认值
}
}
}
- 在 Flutter UI 中使用
main.dart
import 'package:flutter/material.dart';
import 'native_counter_service.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
// 从原生端获取初始值
void initState() {
super.initState();
_getCountFromNative();
}
_getCountFromNative() async {
final count = await NativeCounterService.getCurrentCount();
setState(() {
_counter = count;
});
}
// 增加计数
_incrementCounter() async {
await NativeCounterService.increment(); // 调用原生方法
_getCountFromNative(); // 调用后重新获取最新值
}
// 减少计数
_decrementCounter() async {
await NativeCounterService.decrement(); // 调用原生方法
_getCountFromNative(); // 调用后重新获取最新值
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Native ViewModel Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
const SizedBox(height: 16),
FloatingActionButton(
onPressed: _decrementCounter,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
);
}
}
进阶:使用 EventChannel 实现主动推送(可选)
上面的例子是 Flutter 拉取 数据。如果你想实现原生端 推送 数据(例如,当 LiveData 变化时自动通知 Flutter),可以使用 EventChannel 来监听 LiveData。
这需要更多代码:
- 在 CounterViewModel 中暴露一个 Flow 或继续使用 LiveData。
- 在 MainActivity 中创建一个 EventChannel。
- 将 LiveData 转换为一个可供 EventChannel.StreamHandler 使用的 Flow。
- 在 Flutter 端监听这个 EventChannel 的流。
由于实现较为复杂,且大多数场景下 MethodChannel 的请求-响应模式已足够,这里不再展开。但如果你需要,可以搜索 Flutter EventChannel LiveData 找到相关教程。
总结
通过上述步骤,你成功地在 FlutterActivity 中使用了 Android ViewModel:
- 原生端 (Kotlin/Java):创建了 CounterViewModel 并在 MainActivity 中通过 MethodChannel 暴露其方法。
- 通信桥梁:定义了双方一致的 MethodChannel 名称和方法名 (increment, decrement, getCurrentCount)。
- Flutter 端 (Dart):创建了 NativeCounterService 来封装 Channel 的调用,并在 UI 中触发这些调用并更新状态。
这种模式非常强大,允许你将复杂的、平台相关的业务逻辑(如数据库操作、特定传感器数据访问、后台服务交互等)封装在原生端的 ViewModel 中,而 Flutter 只负责优雅的 UI 展示。