将 Kotlin + ViewModel 迁移到 Flutter
要将现有的 Kotlin + ViewModel 架构迁移到 Flutter,你需要理解 Flutter 的状态管理方式与 Android ViewModel 的区别。以下是一个完整的迁移指南:
1. 架构对比
Android (Kotlin + ViewModel)
- ViewModel: 管理 UI 相关数据,生命周期感知
- LiveData/StateFlow: 观察数据变化
- Data Binding/View Binding: 绑定 UI
Flutter 等效方案
- 状态管理: Provider, Riverpod, Bloc, GetX 等 (替代 ViewModel)
- 状态观察:
ValueNotifier
,Stream
,StatefulWidget
- UI 绑定: Widget 直接使用状态
2. 迁移步骤
2.1 创建等效的状态管理
假设你有一个简单的计数器 ViewModel:
// Android 原代码
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
}
Flutter 等效实现 (使用 Provider):
// Flutter 替代方案
class CounterProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
2.2 迁移 UI 层
Android XML/Kotlin:
// 原 Android 代码
class CounterActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
val counterText = findViewById<TextView>(R.id.counter_text)
val incrementBtn = findViewById<Button>(R.id.increment_btn)
viewModel.count.observe(this) { count ->
counterText.text = count.toString()
}
incrementBtn.setOnClickListener {
viewModel.increment()
}
}
}
Flutter 等效 Widget:
// Flutter 替代代码
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
final counterProvider = Provider.of<CounterProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: ${counterProvider.count}'),
ElevatedButton(
onPressed: () => counterProvider.increment(),
child: Text('Increment'),
),
],
),
),
);
}
}
2.3 设置 Provider
在应用顶层设置 Provider:
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterProvider(),
child: MyApp(),
),
);
}
3. 高级场景迁移
3.1 处理异步操作
原 Kotlin ViewModel 可能包含协程:
class UserViewModel : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user
fun loadUser(userId: String) {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
Flutter 等效:
class UserProvider with ChangeNotifier {
User? _user;
User? get user => _user;
Future<void> loadUser(String userId) async {
_user = await repository.getUser(userId);
notifyListeners();
}
}
3.2 导航迁移
Android 导航:
findNavController().navigate(R.id.action_to_details)
Flutter 导航:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DetailsPage(),
));
4. 推荐的状态管理方案
- Provider + ChangeNotifier: 最接近 ViewModel 的概念
- Riverpod: Provider 的改进版,更灵活
- Bloc: 适合复杂业务逻辑
- GetX: 简单易用,集成路由管理
5. 完整示例
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Counter',
theme: ThemeData(primarySwatch: Colors.blue),
home: CounterPage(),
);
}
}
class CounterProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
final counter = Provider.of<CounterProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pushed the button this many times:'),
Text('${counter.count}', style: Theme.of(context).textTheme.headline4),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counter.increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
6. 迁移建议
- 逐步迁移: 可以一次迁移一个页面,使用 Flutter 的
PlatformView
或MethodChannel
在混合应用中逐步替换 - 业务逻辑复用: 将核心业务逻辑提取为纯 Dart 类,可在两端共享
- 测试验证: 确保迁移后的行为与原生应用一致
通过这种方式,你可以将 Android 的 ViewModel 模式平滑迁移到 Flutter 的状态管理方案中。