You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中MVVM+Repository模式极简实现方案问询

Flutter MVVM + Repository 最简实现(零额外依赖)

实现思路

  • 只用Flutter原生ChangeNotifier做状态通知,完全不需要第三方包
  • 按业务主题拆分专属Repository和ViewModel,每个模块只管对应主题的逻辑
  • 严格遵循你要的双向数据流:Repository -> ViewModel -> View,反向交互则是View -> ViewModel -> Repository

1. 先定义业务模型

以你提到的「挑战」和「自我肯定」为例,先写基础实体类:

// 挑战模型
class Challenge {
  final String id;
  final String title;
  bool isCompleted;

  Challenge({
    required this.id,
    required this.title,
    this.isCompleted = false,
  });

  // 用于更新状态的拷贝方法
  Challenge copyWith({bool? isCompleted}) {
    return Challenge(
      id: id,
      title: title,
      isCompleted: isCompleted ?? this.isCompleted,
    );
  }
}

// 自我肯定模型
class Affirmation {
  final String id;
  final String content;

  Affirmation({
    required this.id,
    required this.content,
  });
}

2. 实现Repository层

每个主题对应一个Repo,负责数据CRUD,并用ChangeNotifier通知ViewModel数据变化:

ChallengeRepository

import 'package:flutter/foundation.dart';
import '../models/challenge.dart';

class ChallengeRepository extends ChangeNotifier {
  // 模拟数据库存储,实际项目可替换为sqflite/API请求
  final List<Challenge> _challenges = [
    Challenge(id: '1', title: '每天步行10000步'),
    Challenge(id: '2', title: '连续7天早起'),
  ];

  // 对外暴露只读数据,避免外部直接修改
  List<Challenge> get challenges => List.unmodifiable(_challenges);

  // 模拟从数据库加载数据
  Future<void> loadChallenges() async {
    await Future.delayed(const Duration(milliseconds: 500)); // 模拟异步延迟
    notifyListeners(); // 加载完成通知ViewModel
  }

  // 更新挑战完成状态
  void toggleChallengeCompletion(String challengeId) {
    final index = _challenges.indexWhere((c) => c.id == challengeId);
    if (index != -1) {
      _challenges[index] = _challenges[index].copyWith(
        isCompleted: !_challenges[index].isCompleted,
      );
      notifyListeners(); // 更新后通知ViewModel
    }
  }

  // 添加新挑战
  void addChallenge(String title) {
    _challenges.add(Challenge(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      title: title,
    ));
    notifyListeners();
  }
}

AffirmationRepository

import 'package:flutter/foundation.dart';
import '../models/affirmation.dart';

class AffirmationRepository extends ChangeNotifier {
  final List<Affirmation> _affirmations = [
    Affirmation(id: '1', content: '我足够优秀'),
    Affirmation(id: '2', content: '我有能力克服困难'),
  ];

  List<Affirmation> get affirmations => List.unmodifiable(_affirmations);

  Future<void> loadAffirmations() async {
    await Future.delayed(const Duration(milliseconds: 500));
    notifyListeners();
  }

  void addAffirmation(String content) {
    _affirmations.add(Affirmation(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      content: content,
    ));
    notifyListeners();
  }
}

3. 实现ViewModel层

ViewModel持有对应Repo实例,监听Repo的变化,更新自身状态,同时给View暴露交互方法:

ChallengeViewModel

import 'package:flutter/foundation.dart';
import '../repositories/challenge_repository.dart';
import '../models/challenge.dart';

class ChallengeViewModel extends ChangeNotifier {
  final ChallengeRepository _repo = ChallengeRepository();
  List<Challenge> _challenges = [];

  List<Challenge> get challenges => _challenges;

  ChallengeViewModel() {
    // 监听Repo的变化,同步更新ViewModel状态
    _repo.addListener(_onRepoUpdated);
    // 初始化加载数据
    loadChallenges();
  }

  // 响应Repo的更新
  void _onRepoUpdated() {
    _challenges = _repo.challenges;
    notifyListeners(); // 通知View刷新UI
  }

  // 给View调用的方法,转发给Repo
  Future<void> loadChallenges() async {
    await _repo.loadChallenges();
  }

  void toggleChallengeCompletion(String challengeId) {
    _repo.toggleChallengeCompletion(challengeId);
  }

  void addChallenge(String title) {
    _repo.addChallenge(title);
  }

  // 销毁时移除监听,避免内存泄漏
  @override
  void dispose() {
    _repo.removeListener(_onRepoUpdated);
    super.dispose();
  }
}

AffirmationViewModel

import 'package:flutter/foundation.dart';
import '../repositories/affirmation_repository.dart';
import '../models/affirmation.dart';

class AffirmationViewModel extends ChangeNotifier {
  final AffirmationRepository _repo = AffirmationRepository();
  List<Affirmation> _affirmations = [];

  List<Affirmation> get affirmations => _affirmations;

  AffirmationViewModel() {
    _repo.addListener(_onRepoUpdated);
    loadAffirmations();
  }

  void _onRepoUpdated() {
    _affirmations = _repo.affirmations;
    notifyListeners();
  }

  Future<void> loadAffirmations() async {
    await _repo.loadAffirmations();
  }

  void addAffirmation(String content) {
    _repo.addAffirmation(content);
  }

  @override
  void dispose() {
    _repo.removeListener(_onRepoUpdated);
    super.dispose();
  }
}

4. 实现View层

用StatefulWidget直接监听ViewModel,完全不用第三方状态管理包:

ChallengeView

import 'package:flutter/material.dart';
import '../viewmodels/challenge_viewmodel.dart';
import '../models/challenge.dart';

class ChallengeView extends StatefulWidget {
  const ChallengeView({super.key});

  @override
  State<ChallengeView> createState() => _ChallengeViewState();
}

class _ChallengeViewState extends State<ChallengeView> {
  late final ChallengeViewModel _viewModel;
  final _textController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _viewModel = ChallengeViewModel();
    // 监听ViewModel变化,刷新UI
    _viewModel.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _viewModel.dispose();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('我的挑战')),
      body: Column(
        children: [
          // 添加挑战输入框
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: const InputDecoration(hintText: '输入新挑战'),
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    if (_textController.text.isNotEmpty) {
                      _viewModel.addChallenge(_textController.text);
                      _textController.clear();
                    }
                  },
                  child: const Text('添加'),
                ),
              ],
            ),
          ),
          // 挑战列表
          Expanded(
            child: _viewModel.challenges.isEmpty
                ? const Center(child: Text('加载中...'))
                : ListView.builder(
                    itemCount: _viewModel.challenges.length,
                    itemBuilder: (context, index) {
                      final Challenge challenge = _viewModel.challenges[index];
                      return ListTile(
                        title: Text(
                          challenge.title,
                          style: TextStyle(
                            decoration: challenge.isCompleted
                                ? TextDecoration.lineThrough
                                : null,
                          ),
                        ),
                        leading: Checkbox(
                          value: challenge.isCompleted,
                          onChanged: (value) {
                            _viewModel.toggleChallengeCompletion(challenge.id);
                          },
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

数据流说明

正向(数据从Repo到View)

  1. Repo加载/更新数据后调用notifyListeners()
  2. ViewModel监听Repo变化,同步自身状态后调用notifyListeners()
  3. View监听ViewModel变化,执行setState()刷新UI

反向(用户交互从View到Repo)

  1. 用户操作UI(比如点击复选框、输入内容)
  2. View调用ViewModel的对应方法
  3. ViewModel转发调用Repo的方法更新数据
  4. Repo更新后触发正向数据流,最终刷新UI

内容的提问来源于stack exchange,提问作者Big_Chair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:29:57