From 8e7487a9489a9e3d87bf614d327e4955f7a7b5f7 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Levesque Date: Tue, 18 Feb 2025 11:32:41 -0500 Subject: [PATCH] chore: Add getFromStream to have ViewModel properties that automatically update based on Stream sources. --- doc/Architecture.md | 6 ++++- .../lib/presentation/mvvm/mvvm_widget.dart | 1 + src/app/lib/presentation/mvvm/view_model.dart | 27 +++++++++++++++++-- src/cli/CHANGELOG.md | 3 +++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/doc/Architecture.md b/doc/Architecture.md index 64ebed97..19490e44 100644 --- a/doc/Architecture.md +++ b/doc/Architecture.md @@ -121,13 +121,17 @@ These defined using accessors that call the `get` (or variants such as `getLazy` Here is an example of a ViewModel showcasing the usage of dynamic properties. ```dart class HomePageViewModel extends ViewModel { - // Regular property don't trigger rebuild if changed. + // Regular properties don't trigger rebuild if changed. final String title = 'Flutter Demo Home Page'; // Dynamic properties triggers rebuild if changed. int get counter => get('counter', 0); set counter(int value) => set('counter', value); + // Dynamic properties can be made using complex sources, such as streams. + int get autoCounter => getFromStream('autoCounter', + () => Stream.periodic(const Duration(seconds: 1), (i) => i), 0); + List get items => getLazy( 'items', () => [ diff --git a/src/app/lib/presentation/mvvm/mvvm_widget.dart b/src/app/lib/presentation/mvvm/mvvm_widget.dart index 5c3c9388..4ff7bd91 100644 --- a/src/app/lib/presentation/mvvm/mvvm_widget.dart +++ b/src/app/lib/presentation/mvvm/mvvm_widget.dart @@ -55,6 +55,7 @@ class _MvvmWidgetState void dispose() { super.dispose(); _viewModel.removeListener(onPropertyChanged); + _viewModel.dispose(); } @override diff --git a/src/app/lib/presentation/mvvm/view_model.dart b/src/app/lib/presentation/mvvm/view_model.dart index 1e9ce569..e4613833 100644 --- a/src/app/lib/presentation/mvvm/view_model.dart +++ b/src/app/lib/presentation/mvvm/view_model.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:collection'; import 'package:flutter/foundation.dart'; @@ -5,17 +6,20 @@ import 'package:flutter/foundation.dart'; abstract class ViewModel extends ChangeNotifier { final Map _properties = {}; final HashSet _propertiesToNotify = HashSet(); + final Map _streamSubscriptions = {}; bool _isRecordingPropertiesToNotify = false; - + void startRecordingPropertiesToNotify() { _propertiesToNotify.clear(); _isRecordingPropertiesToNotify = true; } + void stopRecordingPropertiesToNotify() { _isRecordingPropertiesToNotify = false; } - void _recordPropertyName(String propertyName){ + + void _recordPropertyName(String propertyName) { if (_isRecordingPropertiesToNotify) { _propertiesToNotify.add(propertyName); } @@ -41,4 +45,23 @@ abstract class ViewModel extends ChangeNotifier { _properties[propertyName] = value; notifyPropertyChanged(propertyName); } + + T getFromStream(String propertyName, Stream Function() getStream, T initialValue) { + if (!_streamSubscriptions.containsKey(propertyName)) { + final subscription = getStream().listen((value) { + set(propertyName, value); + }); + _streamSubscriptions[propertyName] = subscription; + } + return get(propertyName, initialValue); + } + + @override + void dispose() { + super.dispose(); + for (final subscription in _streamSubscriptions.values) { + subscription.cancel(); + } + _streamSubscriptions.clear(); + } } diff --git a/src/cli/CHANGELOG.md b/src/cli/CHANGELOG.md index 72b98e2f..022f5618 100644 --- a/src/cli/CHANGELOG.md +++ b/src/cli/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. +## 0.24.1 +- Add `getFromStream` to have ViewModel properties that automatically update based on `Stream` sources. + ## 0.24.0 - Replace Riverpod in favor of a custom MVVM recipe with ViewModels to better align with the recommended [app architecture](https://docs.flutter.dev/app-architecture).