打通 Flutter 与原生状态管理:Android ViewModel 的运用

发布于:2025-09-04 ⋅ 阅读:(17) ⋅ 点赞:(0)

在 FlutterActivity 中使用 ViewModel 是一个结合了 Flutter 和 Android 原生开发(Android Jetpack)的常见场景。这通常用于需要在 Flutter 和原生 Android 代码之间共享状态或逻辑的情况。

核心思路是:

  1. 在 Android 原生端(即在 FlutterActivity 或其附属的 Fragment/Activity 中)创建和管理一个 Android ViewModel。
  2. 通过 MethodChannel 在 Flutter 和原生端之间建立通信桥梁。
  3. Flutter 端通过 MethodChannel 发送指令或请求数据。
  4. 原生端的 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"
    // ... 其他依赖
}
  1. 创建 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
    }
}
  1. 在 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 来与原生端通信。

  1. 创建 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; // 出错时返回默认值
    }
  }
}
  1. 在 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。

这需要更多代码:

  1. 在 CounterViewModel 中暴露一个 Flow 或继续使用 LiveData。
  2. 在 MainActivity 中创建一个 EventChannel。
  3. 将 LiveData 转换为一个可供 EventChannel.StreamHandler 使用的 Flow。
  4. 在 Flutter 端监听这个 EventChannel 的流。

由于实现较为复杂,且大多数场景下 MethodChannel 的请求-响应模式已足够,这里不再展开。但如果你需要,可以搜索 Flutter EventChannel LiveData 找到相关教程。

总结

通过上述步骤,你成功地在 FlutterActivity 中使用了 Android ViewModel:

  1. 原生端 (Kotlin/Java):创建了 CounterViewModel 并在 MainActivity 中通过 MethodChannel 暴露其方法。
  2. 通信桥梁:定义了双方一致的 MethodChannel 名称和方法名 (increment, decrement, getCurrentCount)。
  3. Flutter 端 (Dart):创建了 NativeCounterService 来封装 Channel 的调用,并在 UI 中触发这些调用并更新状态。

这种模式非常强大,允许你将复杂的、平台相关的业务逻辑(如数据库操作、特定传感器数据访问、后台服务交互等)封装在原生端的 ViewModel 中,而 Flutter 只负责优雅的 UI 展示。